@indigoai-us/hq-cli 5.70.0 → 5.72.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/bin/hq-auth-refresh.js +1 -4
  3. package/dist/cli-version.js +1 -4
  4. package/dist/commands/__fixtures__/make-tar.js +1 -4
  5. package/dist/commands/_patterns.js +1 -4
  6. package/dist/commands/add.js +1 -4
  7. package/dist/commands/agents.js +1 -4
  8. package/dist/commands/api-keys.js +1 -4
  9. package/dist/commands/auth.js +1 -4
  10. package/dist/commands/billing.js +1 -4
  11. package/dist/commands/channels.js +1 -4
  12. package/dist/commands/cloud-demote.js +1 -4
  13. package/dist/commands/cloud-provision.js +1 -4
  14. package/dist/commands/cloud.js +1 -4
  15. package/dist/commands/company.js +1 -4
  16. package/dist/commands/creators.js +1 -4
  17. package/dist/commands/crm.js +1 -4
  18. package/dist/commands/db-migrate.js +1 -4
  19. package/dist/commands/db-provision.js +1 -4
  20. package/dist/commands/db-sql.js +1 -4
  21. package/dist/commands/db-status.js +1 -4
  22. package/dist/commands/db.js +1 -4
  23. package/dist/commands/dm.js +1 -4
  24. package/dist/commands/feedback.js +1 -4
  25. package/dist/commands/files-browse.js +1 -4
  26. package/dist/commands/files.js +1 -4
  27. package/dist/commands/group-grants.js +1 -4
  28. package/dist/commands/groups.js +1 -4
  29. package/dist/commands/integrations.js +1 -4
  30. package/dist/commands/list.js +1 -4
  31. package/dist/commands/login.js +1 -4
  32. package/dist/commands/logout.js +1 -4
  33. package/dist/commands/mcp-registration.js +1 -4
  34. package/dist/commands/mcp-status.js +1 -4
  35. package/dist/commands/meetings.js +1 -4
  36. package/dist/commands/members.js +1 -4
  37. package/dist/commands/onboard-identity-guard.js +1 -4
  38. package/dist/commands/onboard-join.js +1 -4
  39. package/dist/commands/onboard-warning.js +1 -4
  40. package/dist/commands/onboard.js +1 -4
  41. package/dist/commands/outposts.d.ts +59 -1
  42. package/dist/commands/outposts.js +319 -10
  43. package/dist/commands/pack-install.js +1 -4
  44. package/dist/commands/packs.js +1 -4
  45. package/dist/commands/people.js +1 -4
  46. package/dist/commands/pkg-install.js +1 -4
  47. package/dist/commands/pkg-list.js +1 -4
  48. package/dist/commands/pkg-remove.js +1 -4
  49. package/dist/commands/pkg-update.js +1 -4
  50. package/dist/commands/publish.js +1 -4
  51. package/dist/commands/reindex.js +1 -4
  52. package/dist/commands/rescue.js +1 -4
  53. package/dist/commands/run.js +1 -4
  54. package/dist/commands/safe-extract.js +1 -4
  55. package/dist/commands/secrets-scope.js +1 -4
  56. package/dist/commands/secrets.js +1 -4
  57. package/dist/commands/signals.js +1 -4
  58. package/dist/commands/skill.js +1 -4
  59. package/dist/commands/sources.js +1 -4
  60. package/dist/commands/sync-mode.js +1 -4
  61. package/dist/commands/sync-narrow.js +1 -4
  62. package/dist/commands/sync.js +1 -4
  63. package/dist/commands/team-sync.js +1 -4
  64. package/dist/commands/update.js +1 -4
  65. package/dist/commands/whoami.js +1 -4
  66. package/dist/commands/workers.js +1 -4
  67. package/dist/index.js +1 -4
  68. package/dist/lib/db/control-plane.js +1 -4
  69. package/dist/lib/db/local.js +1 -4
  70. package/dist/lib/db/migrate.js +1 -4
  71. package/dist/lib/db/paths.js +1 -4
  72. package/dist/lib/db/remote-engine.js +1 -4
  73. package/dist/lib/db/remote-sql.js +1 -4
  74. package/dist/lib/db/sql.js +1 -4
  75. package/dist/lib/local-tree-diff.js +1 -4
  76. package/dist/lib/narrow-hint-banner.js +1 -4
  77. package/dist/main.js +1 -4
  78. package/dist/node-preflight.js +1 -4
  79. package/dist/run/discover-schemas.js +1 -4
  80. package/dist/run/hq-plugin.js +1 -4
  81. package/dist/sentry-before-send.js +1 -4
  82. package/dist/sentry-dsn.generated.js +1 -4
  83. package/dist/sentry.js +1 -4
  84. package/dist/strategies/link.js +1 -4
  85. package/dist/strategies/merge.js +1 -4
  86. package/dist/types.js +1 -4
  87. package/dist/utils/billing-gate.js +1 -4
  88. package/dist/utils/breadcrumb-buffer.js +1 -4
  89. package/dist/utils/cli-telemetry.js +1 -4
  90. package/dist/utils/cognito-session.js +1 -4
  91. package/dist/utils/contribution-table.js +1 -4
  92. package/dist/utils/environmental-error.js +1 -4
  93. package/dist/utils/epipe.js +1 -4
  94. package/dist/utils/feedback-diagnostics.js +1 -4
  95. package/dist/utils/feedback-screenshots.js +1 -4
  96. package/dist/utils/feedback-versions.js +1 -4
  97. package/dist/utils/git.js +1 -4
  98. package/dist/utils/integrity.js +1 -4
  99. package/dist/utils/intercepted-process-exit.js +1 -4
  100. package/dist/utils/login-provider.js +1 -4
  101. package/dist/utils/manifest.js +1 -4
  102. package/dist/utils/pack-contributions.js +1 -4
  103. package/dist/utils/people.js +1 -4
  104. package/dist/utils/registry-client.js +1 -4
  105. package/dist/utils/registry.js +1 -4
  106. package/dist/utils/sandbox-runner-client.js +1 -4
  107. package/dist/utils/secrets-cache.js +1 -4
  108. package/dist/utils/sentry-identity.js +1 -4
  109. package/dist/utils/vault-api.js +1 -4
  110. package/dist/utils/version-check.js +1 -4
  111. package/dist/utils/version-gate.js +1 -4
  112. package/package.json +3 -2
  113. package/scripts/chmod-bins.mjs +49 -0
  114. package/src/commands/outposts-replica-sync.test.ts +272 -0
  115. package/src/commands/outposts.test.ts +252 -4
  116. package/src/commands/outposts.ts +469 -7
  117. package/src/packaging.test.ts +55 -0
@@ -50,9 +50,18 @@ vi.mock("node:child_process", async (importOriginal) => {
50
50
  return { ...original, spawnSync: vi.fn() };
51
51
  });
52
52
 
53
- import { spawnSync } from "node:child_process";
53
+ import { execFileSync, spawnSync } from "node:child_process";
54
+ import * as fs from "node:fs";
55
+ import * as path from "node:path";
54
56
  import { ensureCognitoToken } from "../utils/cognito-session.js";
55
- import { registerOutpostsCommand, withRemoteHqDir } from "./outposts.js";
57
+ import {
58
+ fetchExecResult,
59
+ joinCommandParts,
60
+ registerOutpostsCommand,
61
+ stageExecInput,
62
+ submitExec,
63
+ withRemoteHqDir,
64
+ } from "./outposts.js";
56
65
 
57
66
  const mockSpawnSync = vi.mocked(spawnSync);
58
67
 
@@ -299,7 +308,7 @@ describe("hq outposts exec", () => {
299
308
  expect(init?.method).toBe("POST");
300
309
  // The command is wrapped so it runs from the box's HQ folder.
301
310
  expect(JSON.parse(init?.body as string)).toEqual({
302
- command: withRemoteHqDir("echo hello world"),
311
+ command: withRemoteHqDir("'echo' 'hello' 'world'"),
303
312
  });
304
313
  const printed = stdoutSpy.mock.calls.map((c) => String(c[0])).join("");
305
314
  expect(printed).toContain("hello world\n");
@@ -431,7 +440,7 @@ describe("hq outposts exec — Lightsail SSH fallback", () => {
431
440
  const args = mockSpawnSync.mock.calls[0][1] as string[];
432
441
  expect(mockSpawnSync.mock.calls[0][0]).toBe("ssh");
433
442
  expect(args).toContain("ec2-user@52.2.2.2");
434
- expect(args[args.length - 1]).toBe(withRemoteHqDir("uname -a"));
443
+ expect(args[args.length - 1]).toBe(withRemoteHqDir("'uname' '-a'"));
435
444
  expect(args).toContain("BatchMode=yes");
436
445
  const printed = stdoutSpy.mock.calls.map((c) => String(c[0])).join("");
437
446
  expect(printed).toContain("lightsail-out\n");
@@ -489,3 +498,242 @@ describe("hq outposts exec — Lightsail SSH fallback", () => {
489
498
  expect(mockSpawnSync).not.toHaveBeenCalled();
490
499
  });
491
500
  });
501
+
502
+ describe("hq outposts asynchronous exec", () => {
503
+ it("sends the stage, submit, and result mode request bodies", async () => {
504
+ fetchSpy
505
+ .mockResolvedValueOnce(
506
+ jsonResponse(200, {
507
+ ok: true,
508
+ userId: "u1",
509
+ outpostId: "2",
510
+ key: "input-key",
511
+ putUrl: "https://upload.invalid/put",
512
+ getUrl: "https://download.invalid/get",
513
+ expiresInSeconds: 300,
514
+ }),
515
+ )
516
+ .mockResolvedValueOnce(
517
+ jsonResponse(200, {
518
+ ok: true,
519
+ userId: "u1",
520
+ outpostId: "2",
521
+ instanceId: "i-2",
522
+ commandId: "cmd-1",
523
+ outputPrefix: "output-prefix",
524
+ }),
525
+ )
526
+ .mockResolvedValueOnce(
527
+ jsonResponse(200, {
528
+ ok: true,
529
+ userId: "u1",
530
+ outpostId: "2",
531
+ status: "InProgress",
532
+ done: false,
533
+ }),
534
+ );
535
+
536
+ await stageExecInput("test-token", "2");
537
+ await submitExec("test-token", "cd /tmp && run-worker", "2");
538
+ await fetchExecResult("test-token", "cmd-1", "2");
539
+
540
+ expect(fetchSpy.mock.calls.map(([url]) => String(url))).toEqual([
541
+ expect.stringContaining("/outpost/exec?outpostId=2"),
542
+ expect.stringContaining("/outpost/exec?outpostId=2"),
543
+ expect.stringContaining("/outpost/exec?outpostId=2"),
544
+ ]);
545
+ expect(fetchSpy.mock.calls.map(([, init]) => JSON.parse(init?.body as string))).toEqual([
546
+ { mode: "stage" },
547
+ { mode: "submit", command: "cd /tmp && run-worker" },
548
+ { mode: "result", commandId: "cmd-1" },
549
+ ]);
550
+ });
551
+
552
+ it("exec-stage PUTs the exact file bytes and prints only key + getUrl JSON", async () => {
553
+ const stdoutSpy = vi
554
+ .spyOn(process.stdout, "write")
555
+ .mockImplementation(() => true);
556
+ const file = path.join(process.cwd(), "package.json");
557
+ const expectedBytes = fs.readFileSync(file);
558
+ fetchSpy
559
+ .mockResolvedValueOnce(
560
+ jsonResponse(200, {
561
+ ok: true,
562
+ userId: "u1",
563
+ outpostId: "2",
564
+ key: "input-key",
565
+ putUrl: "https://upload.invalid/put",
566
+ getUrl: "https://download.invalid/get",
567
+ expiresInSeconds: 300,
568
+ }),
569
+ )
570
+ .mockResolvedValueOnce(new Response(null, { status: 200 }));
571
+
572
+ await run(["outposts", "exec-stage", "--id", "2", "--file", file, "--json"]);
573
+
574
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
575
+ const [uploadUrl, uploadInit] = fetchSpy.mock.calls[1];
576
+ expect(String(uploadUrl)).toBe("https://upload.invalid/put");
577
+ expect(uploadInit?.method).toBe("PUT");
578
+ expect(uploadInit?.headers).toEqual({
579
+ "Content-Length": String(expectedBytes.byteLength),
580
+ });
581
+ expect(Buffer.from(uploadInit?.body as Uint8Array)).toEqual(expectedBytes);
582
+ const printed = stdoutSpy.mock.calls.map((call) => String(call[0])).join("");
583
+ expect(JSON.parse(printed)).toEqual({
584
+ key: "input-key",
585
+ getUrl: "https://download.invalid/get",
586
+ });
587
+ });
588
+
589
+ it("exec-stage exits non-zero when the presigned upload fails", async () => {
590
+ fetchSpy
591
+ .mockResolvedValueOnce(
592
+ jsonResponse(200, {
593
+ ok: true,
594
+ userId: "u1",
595
+ outpostId: "primary",
596
+ key: "input-key",
597
+ putUrl: "https://upload.invalid/put",
598
+ getUrl: "https://download.invalid/get",
599
+ expiresInSeconds: 300,
600
+ }),
601
+ )
602
+ .mockResolvedValueOnce(new Response(null, { status: 503 }));
603
+
604
+ await expect(
605
+ run([
606
+ "outposts",
607
+ "exec-stage",
608
+ "--file",
609
+ path.join(process.cwd(), "package.json"),
610
+ "--json",
611
+ ]),
612
+ ).rejects.toThrow("process.exit(1)");
613
+ });
614
+
615
+ it("exec-submit shell-quotes multiple argv parts without adding the HQ-directory wrapper", async () => {
616
+ const stdoutSpy = vi
617
+ .spyOn(process.stdout, "write")
618
+ .mockImplementation(() => true);
619
+ const script = 'printf "%s\\n" "$0" "$1" "$2"';
620
+ fetchSpy.mockResolvedValueOnce(
621
+ jsonResponse(200, {
622
+ ok: true,
623
+ userId: "u1",
624
+ outpostId: "primary",
625
+ instanceId: "i-1",
626
+ commandId: "cmd-submit",
627
+ outputPrefix: "output-prefix",
628
+ }),
629
+ );
630
+
631
+ await run([
632
+ "outposts",
633
+ "exec-submit",
634
+ "--json",
635
+ "--",
636
+ "bash",
637
+ "-c",
638
+ script,
639
+ "marker",
640
+ "a b",
641
+ "quote's",
642
+ ]);
643
+
644
+ const body = JSON.parse(fetchSpy.mock.calls[0][1]?.body as string);
645
+ expect(body).toEqual({
646
+ mode: "submit",
647
+ command: joinCommandParts([
648
+ "bash",
649
+ "-c",
650
+ script,
651
+ "marker",
652
+ "a b",
653
+ "quote's",
654
+ ]),
655
+ });
656
+ expect(body.command).not.toContain("cd ~ec2-user/hq");
657
+ expect(JSON.parse(String(stdoutSpy.mock.calls[0][0]))).toEqual({
658
+ commandId: "cmd-submit",
659
+ });
660
+ });
661
+
662
+ it("preserves one command part verbatim and round-trips quoted bash argv", () => {
663
+ const single = " printf 'already formed' ";
664
+ expect(joinCommandParts([single])).toBe(single);
665
+
666
+ const script = 'printf "%s\\n" "$0" "$1" "$2"';
667
+ const command = joinCommandParts([
668
+ "bash",
669
+ "-c",
670
+ script,
671
+ "marker",
672
+ "a b",
673
+ "quote's",
674
+ ]);
675
+ expect(execFileSync("bash", ["-c", command], { encoding: "utf8" })).toBe(
676
+ "marker\na b\nquote's\n",
677
+ );
678
+ });
679
+
680
+ it("exec-result --wait retries a 429, polls until done, and prints the result contract", async () => {
681
+ vi.useFakeTimers();
682
+ try {
683
+ const stdoutSpy = vi
684
+ .spyOn(process.stdout, "write")
685
+ .mockImplementation(() => true);
686
+ fetchSpy
687
+ .mockResolvedValueOnce(
688
+ jsonResponse(429, { error: true, step: "rate", message: "slow down" }),
689
+ )
690
+ .mockResolvedValueOnce(
691
+ jsonResponse(200, {
692
+ ok: true,
693
+ userId: "u1",
694
+ outpostId: "2",
695
+ status: "InProgress",
696
+ done: false,
697
+ }),
698
+ )
699
+ .mockResolvedValueOnce(
700
+ jsonResponse(200, {
701
+ ok: true,
702
+ userId: "u1",
703
+ outpostId: "2",
704
+ status: "Success",
705
+ done: true,
706
+ exitCode: 0,
707
+ stdout: "finished\n",
708
+ stderr: "",
709
+ truncated: false,
710
+ }),
711
+ );
712
+
713
+ const pending = run([
714
+ "outposts",
715
+ "exec-result",
716
+ "--id",
717
+ "2",
718
+ "--command-id",
719
+ "cmd-1",
720
+ "--wait",
721
+ "--json",
722
+ ]);
723
+ await vi.advanceTimersByTimeAsync(500);
724
+ await vi.advanceTimersByTimeAsync(1_000);
725
+ await pending;
726
+
727
+ expect(fetchSpy).toHaveBeenCalledTimes(3);
728
+ expect(JSON.parse(String(stdoutSpy.mock.calls[0][0]))).toEqual({
729
+ done: true,
730
+ status: "Success",
731
+ exitCode: 0,
732
+ stdout: "finished\n",
733
+ stderr: "",
734
+ });
735
+ } finally {
736
+ vi.useRealTimers();
737
+ }
738
+ });
739
+ });