@indigoai-us/hq-cloud 5.1.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 (108) hide show
  1. package/dist/auth.d.ts +21 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +116 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/cli/accept.d.ts +29 -0
  6. package/dist/cli/accept.d.ts.map +1 -0
  7. package/dist/cli/accept.js +67 -0
  8. package/dist/cli/accept.js.map +1 -0
  9. package/dist/cli/conflict.d.ts +33 -0
  10. package/dist/cli/conflict.d.ts.map +1 -0
  11. package/dist/cli/conflict.js +91 -0
  12. package/dist/cli/conflict.js.map +1 -0
  13. package/dist/cli/index.d.ts +19 -0
  14. package/dist/cli/index.d.ts.map +1 -0
  15. package/dist/cli/index.js +14 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/cli/invite.d.ts +51 -0
  18. package/dist/cli/invite.d.ts.map +1 -0
  19. package/dist/cli/invite.js +120 -0
  20. package/dist/cli/invite.js.map +1 -0
  21. package/dist/cli/invite.test.d.ts +5 -0
  22. package/dist/cli/invite.test.d.ts.map +1 -0
  23. package/dist/cli/invite.test.js +175 -0
  24. package/dist/cli/invite.test.js.map +1 -0
  25. package/dist/cli/promote.d.ts +30 -0
  26. package/dist/cli/promote.d.ts.map +1 -0
  27. package/dist/cli/promote.js +79 -0
  28. package/dist/cli/promote.js.map +1 -0
  29. package/dist/cli/share.d.ts +33 -0
  30. package/dist/cli/share.d.ts.map +1 -0
  31. package/dist/cli/share.js +153 -0
  32. package/dist/cli/share.js.map +1 -0
  33. package/dist/cli/share.test.d.ts +5 -0
  34. package/dist/cli/share.test.d.ts.map +1 -0
  35. package/dist/cli/share.test.js +121 -0
  36. package/dist/cli/share.test.js.map +1 -0
  37. package/dist/cli/sync.d.ts +30 -0
  38. package/dist/cli/sync.d.ts.map +1 -0
  39. package/dist/cli/sync.js +138 -0
  40. package/dist/cli/sync.js.map +1 -0
  41. package/dist/cli/sync.test.d.ts +5 -0
  42. package/dist/cli/sync.test.d.ts.map +1 -0
  43. package/dist/cli/sync.test.js +172 -0
  44. package/dist/cli/sync.test.js.map +1 -0
  45. package/dist/cognito-auth.d.ts +70 -0
  46. package/dist/cognito-auth.d.ts.map +1 -0
  47. package/dist/cognito-auth.js +280 -0
  48. package/dist/cognito-auth.js.map +1 -0
  49. package/dist/context.d.ts +30 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/context.js +117 -0
  52. package/dist/context.js.map +1 -0
  53. package/dist/context.test.d.ts +7 -0
  54. package/dist/context.test.d.ts.map +1 -0
  55. package/dist/context.test.js +148 -0
  56. package/dist/context.test.js.map +1 -0
  57. package/dist/daemon-worker.d.ts +6 -0
  58. package/dist/daemon-worker.d.ts.map +1 -0
  59. package/dist/daemon-worker.js +26 -0
  60. package/dist/daemon-worker.js.map +1 -0
  61. package/dist/daemon.d.ts +10 -0
  62. package/dist/daemon.d.ts.map +1 -0
  63. package/dist/daemon.js +88 -0
  64. package/dist/daemon.js.map +1 -0
  65. package/dist/ignore.d.ts +10 -0
  66. package/dist/ignore.d.ts.map +1 -0
  67. package/dist/ignore.js +54 -0
  68. package/dist/ignore.js.map +1 -0
  69. package/dist/index.d.ts +33 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +138 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/journal.d.ts +12 -0
  74. package/dist/journal.d.ts.map +1 -0
  75. package/dist/journal.js +42 -0
  76. package/dist/journal.js.map +1 -0
  77. package/dist/s3.d.ts +15 -0
  78. package/dist/s3.d.ts.map +1 -0
  79. package/dist/s3.js +129 -0
  80. package/dist/s3.js.map +1 -0
  81. package/dist/types.d.ts +52 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +5 -0
  84. package/dist/types.js.map +1 -0
  85. package/dist/vault-client.d.ts +164 -0
  86. package/dist/vault-client.d.ts.map +1 -0
  87. package/dist/vault-client.js +209 -0
  88. package/dist/vault-client.js.map +1 -0
  89. package/dist/vault-client.test.d.ts +7 -0
  90. package/dist/vault-client.test.d.ts.map +1 -0
  91. package/dist/vault-client.test.js +257 -0
  92. package/dist/vault-client.test.js.map +1 -0
  93. package/dist/watcher.d.ts +18 -0
  94. package/dist/watcher.d.ts.map +1 -0
  95. package/dist/watcher.js +106 -0
  96. package/dist/watcher.js.map +1 -0
  97. package/package.json +32 -0
  98. package/src/auth.ts +146 -0
  99. package/src/cognito-auth.ts +375 -0
  100. package/src/daemon-worker.ts +32 -0
  101. package/src/daemon.ts +97 -0
  102. package/src/ignore.ts +61 -0
  103. package/src/index.ts +182 -0
  104. package/src/journal.ts +63 -0
  105. package/src/s3.ts +178 -0
  106. package/src/types.ts +59 -0
  107. package/src/watcher.ts +130 -0
  108. package/tsconfig.json +8 -0
@@ -0,0 +1,257 @@
1
+ /**
2
+ * VaultClient unit tests (VLT-7 US-001).
3
+ *
4
+ * Uses mocked fetch to assert retry behavior, error mapping, and auth header injection.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
7
+ import { VaultClient, VaultAuthError, VaultPermissionDeniedError, VaultNotFoundError, VaultConflictError, VaultClientError, } from "./vault-client.js";
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+ function jsonResponse(status, body) {
12
+ return new Response(JSON.stringify(body), {
13
+ status,
14
+ headers: { "Content-Type": "application/json" },
15
+ });
16
+ }
17
+ function textResponse(status, body) {
18
+ return new Response(body, { status });
19
+ }
20
+ const TEST_CONFIG = {
21
+ apiUrl: "https://vault.test.example.com",
22
+ authToken: "test-jwt-token-123",
23
+ };
24
+ let client;
25
+ let fetchSpy;
26
+ beforeEach(() => {
27
+ client = new VaultClient(TEST_CONFIG);
28
+ fetchSpy = vi.spyOn(globalThis, "fetch");
29
+ });
30
+ afterEach(() => {
31
+ vi.restoreAllMocks();
32
+ });
33
+ // ---------------------------------------------------------------------------
34
+ // Auth header injection
35
+ // ---------------------------------------------------------------------------
36
+ describe("auth header injection", () => {
37
+ it("sends Bearer token on every request", async () => {
38
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { members: [] }));
39
+ await client.listMembersOfCompany("cmp_abc");
40
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
41
+ const [, init] = fetchSpy.mock.calls[0];
42
+ const headers = init.headers;
43
+ expect(headers.Authorization).toBe("Bearer test-jwt-token-123");
44
+ });
45
+ it("sets Content-Type on POST requests", async () => {
46
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { membership: {}, inviteToken: "tok" }));
47
+ await client.createInvite({
48
+ companyUid: "cmp_abc",
49
+ role: "member",
50
+ invitedBy: "psn_xyz",
51
+ });
52
+ const [, init] = fetchSpy.mock.calls[0];
53
+ const headers = init.headers;
54
+ expect(headers["Content-Type"]).toBe("application/json");
55
+ });
56
+ });
57
+ // ---------------------------------------------------------------------------
58
+ // Error mapping
59
+ // ---------------------------------------------------------------------------
60
+ describe("error mapping", () => {
61
+ it("maps 401 to VaultAuthError", async () => {
62
+ fetchSpy.mockResolvedValueOnce(jsonResponse(401, { message: "Token expired" }));
63
+ await expect(client.listMembersOfCompany("cmp_abc")).rejects.toThrow(VaultAuthError);
64
+ });
65
+ it("maps 403 to VaultPermissionDeniedError", async () => {
66
+ fetchSpy.mockResolvedValueOnce(jsonResponse(403, { message: "Admin required" }));
67
+ await expect(client.listMembersOfCompany("cmp_abc")).rejects.toThrow(VaultPermissionDeniedError);
68
+ });
69
+ it("maps 404 to VaultNotFoundError", async () => {
70
+ fetchSpy.mockResolvedValueOnce(jsonResponse(404, { message: "Not found" }));
71
+ await expect(client.entity.get("cmp_missing")).rejects.toThrow(VaultNotFoundError);
72
+ });
73
+ it("maps 409 to VaultConflictError", async () => {
74
+ fetchSpy.mockResolvedValueOnce(jsonResponse(409, { message: "Already accepted" }));
75
+ await expect(client.acceptInvite("tok", "psn_abc")).rejects.toThrow(VaultConflictError);
76
+ });
77
+ it("preserves error message from response body", async () => {
78
+ fetchSpy.mockResolvedValueOnce(jsonResponse(403, { message: "Only admins can invite" }));
79
+ try {
80
+ await client.createInvite({
81
+ companyUid: "cmp_abc",
82
+ role: "member",
83
+ invitedBy: "psn_xyz",
84
+ });
85
+ expect.fail("Should have thrown");
86
+ }
87
+ catch (err) {
88
+ expect(err).toBeInstanceOf(VaultPermissionDeniedError);
89
+ expect(err.message).toBe("Only admins can invite");
90
+ }
91
+ });
92
+ it("handles non-JSON error bodies gracefully", async () => {
93
+ fetchSpy.mockResolvedValueOnce(textResponse(404, "Not Found"));
94
+ try {
95
+ await client.entity.findBySlug("company", "test");
96
+ expect.fail("Should have thrown");
97
+ }
98
+ catch (err) {
99
+ expect(err).toBeInstanceOf(VaultNotFoundError);
100
+ expect(err.message).toBe("Not Found");
101
+ }
102
+ });
103
+ });
104
+ // ---------------------------------------------------------------------------
105
+ // Retry behavior
106
+ // ---------------------------------------------------------------------------
107
+ describe("retry behavior", () => {
108
+ it("retries on 429 and succeeds on second attempt", async () => {
109
+ fetchSpy
110
+ .mockResolvedValueOnce(jsonResponse(429, { message: "Rate limited" }))
111
+ .mockResolvedValueOnce(jsonResponse(200, { members: [{ personUid: "psn_1" }] }));
112
+ const result = await client.listMembersOfCompany("cmp_abc");
113
+ expect(result).toHaveLength(1);
114
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
115
+ });
116
+ it("retries on 500 and succeeds on third attempt", async () => {
117
+ fetchSpy
118
+ .mockResolvedValueOnce(jsonResponse(500, { message: "Internal error" }))
119
+ .mockResolvedValueOnce(jsonResponse(502, { message: "Bad gateway" }))
120
+ .mockResolvedValueOnce(jsonResponse(200, { membership: { role: "admin" } }));
121
+ const result = await client.updateRole({
122
+ membershipKey: "psn_1#cmp_abc",
123
+ newRole: "admin",
124
+ updaterUid: "psn_owner",
125
+ companyUid: "cmp_abc",
126
+ });
127
+ expect(result.role).toBe("admin");
128
+ expect(fetchSpy).toHaveBeenCalledTimes(3);
129
+ });
130
+ it("throws after exhausting all retries on persistent 500", async () => {
131
+ fetchSpy.mockImplementation(() => Promise.resolve(jsonResponse(500, { message: "Down" })));
132
+ await expect(client.listMembersOfCompany("cmp_abc")).rejects.toThrow(VaultClientError);
133
+ // 1 initial + 3 retries = 4
134
+ expect(fetchSpy).toHaveBeenCalledTimes(4);
135
+ });
136
+ it("does not retry on 401 (non-transient)", async () => {
137
+ fetchSpy.mockResolvedValueOnce(jsonResponse(401, { message: "Expired" }));
138
+ await expect(client.listMembersOfCompany("cmp_abc")).rejects.toThrow(VaultAuthError);
139
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
140
+ });
141
+ it("does not retry on 403 (non-transient)", async () => {
142
+ fetchSpy.mockResolvedValueOnce(jsonResponse(403, { message: "Forbidden" }));
143
+ await expect(client.createInvite({
144
+ companyUid: "cmp_abc",
145
+ role: "member",
146
+ invitedBy: "psn_xyz",
147
+ })).rejects.toThrow(VaultPermissionDeniedError);
148
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
149
+ });
150
+ it("retries on network errors (fetch throws)", async () => {
151
+ fetchSpy
152
+ .mockRejectedValueOnce(new Error("ECONNRESET"))
153
+ .mockResolvedValueOnce(jsonResponse(200, { members: [] }));
154
+ const result = await client.listMembersOfCompany("cmp_abc");
155
+ expect(result).toEqual([]);
156
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
157
+ });
158
+ });
159
+ // ---------------------------------------------------------------------------
160
+ // API surface
161
+ // ---------------------------------------------------------------------------
162
+ describe("API surface", () => {
163
+ it("createInvite sends correct body and URL", async () => {
164
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, {
165
+ membership: { membershipKey: "psn_1#cmp_abc", role: "member", status: "pending" },
166
+ inviteToken: "tok_secure_random",
167
+ }));
168
+ const result = await client.createInvite({
169
+ companyUid: "cmp_abc",
170
+ role: "member",
171
+ invitedBy: "psn_owner",
172
+ inviteeEmail: "alice@example.com",
173
+ });
174
+ expect(result.inviteToken).toBe("tok_secure_random");
175
+ const [url, init] = fetchSpy.mock.calls[0];
176
+ expect(url).toBe("https://vault.test.example.com/membership/invite");
177
+ expect(JSON.parse(init.body)).toEqual({
178
+ companyUid: "cmp_abc",
179
+ role: "member",
180
+ invitedBy: "psn_owner",
181
+ inviteeEmail: "alice@example.com",
182
+ });
183
+ });
184
+ it("acceptInvite sends token and personUid", async () => {
185
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, {
186
+ membership: { status: "active", role: "member" },
187
+ }));
188
+ const result = await client.acceptInvite("tok_abc", "psn_invitee");
189
+ expect(result.membership.status).toBe("active");
190
+ const [url, init] = fetchSpy.mock.calls[0];
191
+ expect(url).toBe("https://vault.test.example.com/membership/accept");
192
+ expect(JSON.parse(init.body)).toEqual({
193
+ token: "tok_abc",
194
+ personUid: "psn_invitee",
195
+ });
196
+ });
197
+ it("updateRole sends correct payload", async () => {
198
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, {
199
+ membership: { role: "guest", allowedPrefixes: ["docs/"] },
200
+ }));
201
+ const result = await client.updateRole({
202
+ membershipKey: "psn_1#cmp_abc",
203
+ newRole: "guest",
204
+ allowedPrefixes: ["docs/"],
205
+ updaterUid: "psn_admin",
206
+ companyUid: "cmp_abc",
207
+ });
208
+ expect(result.role).toBe("guest");
209
+ expect(result.allowedPrefixes).toEqual(["docs/"]);
210
+ const [url, init] = fetchSpy.mock.calls[0];
211
+ expect(url).toBe("https://vault.test.example.com/membership/role");
212
+ expect(JSON.parse(init.body)).toEqual({
213
+ membershipKey: "psn_1#cmp_abc",
214
+ newRole: "guest",
215
+ allowedPrefixes: ["docs/"],
216
+ updaterUid: "psn_admin",
217
+ companyUid: "cmp_abc",
218
+ });
219
+ });
220
+ it("entity.get calls correct URL", async () => {
221
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, {
222
+ entity: { uid: "cmp_abc", slug: "acme", type: "company", status: "active" },
223
+ }));
224
+ const entity = await client.entity.get("cmp_abc");
225
+ expect(entity.slug).toBe("acme");
226
+ const [url] = fetchSpy.mock.calls[0];
227
+ expect(url).toBe("https://vault.test.example.com/entity/cmp_abc");
228
+ });
229
+ it("entity.findBySlug calls correct URL", async () => {
230
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, {
231
+ entity: { uid: "cmp_abc", slug: "acme", type: "company", status: "active" },
232
+ }));
233
+ const entity = await client.entity.findBySlug("company", "acme");
234
+ expect(entity.uid).toBe("cmp_abc");
235
+ const [url] = fetchSpy.mock.calls[0];
236
+ expect(url).toBe("https://vault.test.example.com/entity/by-slug/company/acme");
237
+ });
238
+ it("revokeMembership calls POST /membership/revoke with companyUid", async () => {
239
+ fetchSpy.mockResolvedValueOnce(new Response(null, { status: 204 }));
240
+ await client.revokeMembership("psn_1#cmp_abc", "cmp_abc");
241
+ const [url, init] = fetchSpy.mock.calls[0];
242
+ expect(url).toBe("https://vault.test.example.com/membership/revoke");
243
+ expect(init.method).toBe("POST");
244
+ expect(JSON.parse(init.body)).toEqual({
245
+ membershipKey: "psn_1#cmp_abc",
246
+ companyUid: "cmp_abc",
247
+ });
248
+ });
249
+ it("listPendingInvites calls correct URL", async () => {
250
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { invites: [{ status: "pending" }] }));
251
+ const invites = await client.listPendingInvites("cmp_abc");
252
+ expect(invites).toHaveLength(1);
253
+ const [url] = fetchSpy.mock.calls[0];
254
+ expect(url).toBe("https://vault.test.example.com/membership/company/cmp_abc/pending");
255
+ });
256
+ });
257
+ //# sourceMappingURL=vault-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault-client.test.js","sourceRoot":"","sources":["../src/vault-client.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAqB,MAAM,QAAQ,CAAC;AAC5F,OAAO,EACL,WAAW,EACX,cAAc,EACd,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,YAAY,CAAC,MAAc,EAAE,IAAa;IACjD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,MAAc,EAAE,IAAY;IAChD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,gCAAgC;IACxC,SAAS,EAAE,oBAAoB;CAChC,CAAC;AAEF,IAAI,MAAmB,CAAC;AACxB,IAAI,QAAoC,CAAC;AAEzC,UAAU,CAAC,GAAG,EAAE;IACd,MAAM,GAAG,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;IACtC,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,EAAE,CAAC,eAAe,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CACnC,CAAC;QAEF,MAAM,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAE7C,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAC;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAiC,CAAC;QACvD,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAC1D,CAAC;QAEF,MAAM,MAAM,CAAC,YAAY,CAAC;YACxB,UAAU,EAAE,SAAS;YACrB,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,SAAS;SACrB,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAC;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAiC,CAAC;QACvD,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAChD,CAAC;QAEF,MAAM,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CACjD,CAAC;QAEF,MAAM,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACnG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAC5C,CAAC;QAEF,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CACnD,CAAC;QAEF,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CACzD,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,YAAY,CAAC;gBACxB,UAAU,EAAE,SAAS;gBACrB,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,SAAS;aACrB,CAAC,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,0BAA0B,CAAC,CAAC;YACvD,MAAM,CAAE,GAAkC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACrF,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,WAAW,CAAC,CAC/B,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC;YAC/C,MAAM,CAAE,GAA0B,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,QAAQ;aACL,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;aACrE,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAEnF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,QAAQ;aACL,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;aACvE,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;aACpE,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QAE/E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC;YACrC,aAAa,EAAE,eAAe;YAC9B,OAAO,EAAE,OAAO;YAChB,UAAU,EAAE,WAAW;YACvB,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,QAAQ,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAC/B,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CACxD,CAAC;QAEF,MAAM,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACvF,4BAA4B;QAC5B,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,QAAQ,CAAC,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAE1E,MAAM,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACrF,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,QAAQ,CAAC,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;QAE5E,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;YAC/B,UAAU,EAAE,SAAS;YACrB,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,SAAS;SACrB,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,QAAQ;aACL,qBAAqB,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;aAC9C,qBAAqB,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAE7D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE;YAChB,UAAU,EAAE,EAAE,aAAa,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE;YACjF,WAAW,EAAE,mBAAmB;SACjC,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC;YACvC,UAAU,EAAE,SAAS;YACrB,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,WAAW;YACtB,YAAY,EAAE,mBAAmB;SAClC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAErD,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAc,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9C,UAAU,EAAE,SAAS;YACrB,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,WAAW;YACtB,YAAY,EAAE,mBAAmB;SAClC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE;YAChB,UAAU,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE;SACjD,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEhD,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAc,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9C,KAAK,EAAE,SAAS;YAChB,SAAS,EAAE,aAAa;SACzB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE;YAChB,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE;SAC1D,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC;YACrC,aAAa,EAAE,eAAe;YAC9B,OAAO,EAAE,OAAO;YAChB,eAAe,EAAE,CAAC,OAAO,CAAC;YAC1B,UAAU,EAAE,WAAW;YACvB,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QAElD,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QACnE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAc,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9C,aAAa,EAAE,eAAe;YAC9B,OAAO,EAAE,OAAO;YAChB,eAAe,EAAE,CAAC,OAAO,CAAC;YAC1B,UAAU,EAAE,WAAW;YACvB,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC5C,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE;YAChB,MAAM,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;SAC5E,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEjC,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE;YAChB,MAAM,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;SAC5E,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACjE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEnC,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,QAAQ,CAAC,qBAAqB,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAEpE,MAAM,MAAM,CAAC,gBAAgB,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;QAE1D,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAc,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9C,aAAa,EAAE,eAAe;YAC9B,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,QAAQ,CAAC,qBAAqB,CAC5B,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CACxD,CAAC;QAEF,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAEhC,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAa,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * File watcher — monitors HQ directory for changes
3
+ * Uses chokidar with debounced batching
4
+ */
5
+ export declare class SyncWatcher {
6
+ private watcher;
7
+ private hqRoot;
8
+ private shouldSync;
9
+ private pendingChanges;
10
+ private debounceTimer;
11
+ private processing;
12
+ constructor(hqRoot: string);
13
+ start(): void;
14
+ stop(): void;
15
+ private queueChange;
16
+ private flush;
17
+ }
18
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,MAAM;IAK1B,KAAK,IAAI,IAAI;IAoBb,IAAI,IAAI,IAAI;IAWZ,OAAO,CAAC,WAAW;YAqBL,KAAK;CA2CpB"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * File watcher — monitors HQ directory for changes
3
+ * Uses chokidar with debounced batching
4
+ */
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { watch } from "chokidar";
8
+ import { createIgnoreFilter, isWithinSizeLimit } from "./ignore.js";
9
+ import { readJournal, writeJournal, hashFile, updateEntry } from "./journal.js";
10
+ import { uploadFile, deleteRemoteFile } from "./s3.js";
11
+ const DEBOUNCE_MS = 2000;
12
+ export class SyncWatcher {
13
+ watcher = null;
14
+ hqRoot;
15
+ shouldSync;
16
+ pendingChanges = new Map();
17
+ debounceTimer = null;
18
+ processing = false;
19
+ constructor(hqRoot) {
20
+ this.hqRoot = hqRoot;
21
+ this.shouldSync = createIgnoreFilter(hqRoot);
22
+ }
23
+ start() {
24
+ if (this.watcher)
25
+ return;
26
+ this.watcher = watch(this.hqRoot, {
27
+ ignored: (filePath) => !this.shouldSync(filePath),
28
+ persistent: true,
29
+ ignoreInitial: true,
30
+ awaitWriteFinish: {
31
+ stabilityThreshold: 500,
32
+ pollInterval: 100,
33
+ },
34
+ });
35
+ this.watcher
36
+ .on("add", (p) => this.queueChange("add", p))
37
+ .on("change", (p) => this.queueChange("change", p))
38
+ .on("unlink", (p) => this.queueChange("unlink", p))
39
+ .on("error", (err) => console.error("Watcher error:", err));
40
+ }
41
+ stop() {
42
+ if (this.watcher) {
43
+ this.watcher.close();
44
+ this.watcher = null;
45
+ }
46
+ if (this.debounceTimer) {
47
+ clearTimeout(this.debounceTimer);
48
+ this.debounceTimer = null;
49
+ }
50
+ }
51
+ queueChange(type, absolutePath) {
52
+ const relativePath = path.relative(this.hqRoot, absolutePath);
53
+ // Skip files that exceed size limit
54
+ if (type !== "unlink" && !isWithinSizeLimit(absolutePath)) {
55
+ return;
56
+ }
57
+ this.pendingChanges.set(relativePath, {
58
+ type,
59
+ absolutePath,
60
+ relativePath,
61
+ });
62
+ // Debounce: wait for DEBOUNCE_MS of quiet before processing
63
+ if (this.debounceTimer) {
64
+ clearTimeout(this.debounceTimer);
65
+ }
66
+ this.debounceTimer = setTimeout(() => this.flush(), DEBOUNCE_MS);
67
+ }
68
+ async flush() {
69
+ if (this.processing || this.pendingChanges.size === 0)
70
+ return;
71
+ this.processing = true;
72
+ const batch = new Map(this.pendingChanges);
73
+ this.pendingChanges.clear();
74
+ const journal = readJournal(this.hqRoot);
75
+ for (const [relativePath, change] of batch) {
76
+ try {
77
+ if (change.type === "unlink") {
78
+ await deleteRemoteFile(relativePath);
79
+ delete journal.files[relativePath];
80
+ }
81
+ else {
82
+ const hash = hashFile(change.absolutePath);
83
+ const stat = fs.statSync(change.absolutePath);
84
+ // Skip if unchanged from last sync
85
+ const existing = journal.files[relativePath];
86
+ if (existing && existing.hash === hash)
87
+ continue;
88
+ await uploadFile(change.absolutePath, relativePath);
89
+ updateEntry(journal, relativePath, hash, stat.size, "up");
90
+ }
91
+ }
92
+ catch (err) {
93
+ console.error(`Sync error [${relativePath}]:`, err instanceof Error ? err.message : err);
94
+ // Re-queue failed changes
95
+ this.pendingChanges.set(relativePath, change);
96
+ }
97
+ }
98
+ writeJournal(this.hqRoot, journal);
99
+ this.processing = false;
100
+ // Process any changes that came in while we were flushing
101
+ if (this.pendingChanges.size > 0) {
102
+ this.debounceTimer = setTimeout(() => this.flush(), DEBOUNCE_MS);
103
+ }
104
+ }
105
+ }
106
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@indigoai-us/hq-cloud",
3
+ "version": "5.1.0",
4
+ "description": "HQ by Indigo cloud sync engine — bidirectional S3 sync for mobile access",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "typecheck": "tsc --noEmit",
10
+ "clean": "rm -rf dist"
11
+ },
12
+ "dependencies": {
13
+ "@aws-sdk/client-s3": "^3.700.0",
14
+ "@aws-sdk/client-cognito-identity": "^3.700.0",
15
+ "@aws-sdk/credential-providers": "^3.700.0",
16
+ "chokidar": "^4.0.0",
17
+ "ignore": "^6.0.0",
18
+ "open": "^10.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.0.0",
22
+ "typescript": "^5.7.0"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/indigoai-us/hq.git",
27
+ "directory": "packages/hq-cloud"
28
+ },
29
+ "keywords": ["hq", "sync", "s3", "cloud"],
30
+ "license": "MIT",
31
+ "type": "module"
32
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Authentication — OAuth flow with IndigoAI
3
+ * Opens browser for sign-in, receives tokens via localhost callback
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import * as http from "http";
9
+ import open from "open";
10
+ import type { Credentials } from "./types.js";
11
+
12
+ const AUTH_URL = "https://hq.indigoai.com/auth";
13
+ const CALLBACK_PORT = 19847;
14
+ const CREDS_DIR = path.join(
15
+ process.env.HOME || process.env.USERPROFILE || "~",
16
+ ".hq"
17
+ );
18
+ const CREDS_FILE = path.join(CREDS_DIR, "credentials.json");
19
+
20
+ export function hasCredentials(): boolean {
21
+ return fs.existsSync(CREDS_FILE);
22
+ }
23
+
24
+ export function readCredentials(): Credentials | null {
25
+ if (!fs.existsSync(CREDS_FILE)) return null;
26
+ try {
27
+ const content = fs.readFileSync(CREDS_FILE, "utf-8");
28
+ return JSON.parse(content) as Credentials;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ export function writeCredentials(creds: Credentials): void {
35
+ if (!fs.existsSync(CREDS_DIR)) {
36
+ fs.mkdirSync(CREDS_DIR, { recursive: true });
37
+ }
38
+ fs.writeFileSync(CREDS_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
39
+ }
40
+
41
+ export function clearCredentials(): void {
42
+ if (fs.existsSync(CREDS_FILE)) {
43
+ fs.unlinkSync(CREDS_FILE);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Start OAuth flow:
49
+ * 1. Open browser to AUTH_URL with callback port
50
+ * 2. Start localhost server to receive tokens
51
+ * 3. Store credentials
52
+ */
53
+ export async function authenticate(): Promise<Credentials> {
54
+ return new Promise((resolve, reject) => {
55
+ const server = http.createServer((req, res) => {
56
+ const url = new URL(req.url || "", `http://localhost:${CALLBACK_PORT}`);
57
+
58
+ if (url.pathname === "/callback") {
59
+ const token = url.searchParams.get("token");
60
+ const refreshToken = url.searchParams.get("refresh_token");
61
+ const userId = url.searchParams.get("user_id");
62
+ const bucket = url.searchParams.get("bucket");
63
+ const region = url.searchParams.get("region") || "us-east-1";
64
+ const teamId = url.searchParams.get("team_id");
65
+
66
+ if (!token || !refreshToken || !userId || !bucket) {
67
+ res.writeHead(400, { "Content-Type": "text/html" });
68
+ res.end("<h1>Authentication failed</h1><p>Missing required parameters. Please try again.</p>");
69
+ server.close();
70
+ reject(new Error("Authentication failed: missing parameters"));
71
+ return;
72
+ }
73
+
74
+ const creds: Credentials = {
75
+ accessKeyId: "", // Will be populated via STS
76
+ secretAccessKey: "",
77
+ refreshToken,
78
+ userId,
79
+ bucket,
80
+ region,
81
+ ...(teamId ? { teamId } : {}),
82
+ };
83
+
84
+ writeCredentials(creds);
85
+
86
+ res.writeHead(200, { "Content-Type": "text/html" });
87
+ res.end(
88
+ "<h1>Authenticated!</h1><p>You can close this window and return to your terminal.</p>"
89
+ );
90
+
91
+ server.close();
92
+ resolve(creds);
93
+ }
94
+ });
95
+
96
+ server.listen(CALLBACK_PORT, () => {
97
+ const authUrl = `${AUTH_URL}?callback=http://localhost:${CALLBACK_PORT}/callback`;
98
+ console.log(` Opening browser for authentication...`);
99
+ console.log(` If browser doesn't open, visit: ${authUrl}`);
100
+ open(authUrl).catch(() => {
101
+ // If open fails, user can manually visit the URL
102
+ });
103
+ });
104
+
105
+ // Timeout after 5 minutes
106
+ setTimeout(() => {
107
+ server.close();
108
+ reject(new Error("Authentication timed out after 5 minutes"));
109
+ }, 5 * 60 * 1000);
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Refresh temporary AWS credentials using refresh token
115
+ */
116
+ export async function refreshAwsCredentials(
117
+ creds: Credentials
118
+ ): Promise<Credentials> {
119
+ const response = await fetch("https://hq.indigoai.com/api/auth/refresh", {
120
+ method: "POST",
121
+ headers: { "Content-Type": "application/json" },
122
+ body: JSON.stringify({ refreshToken: creds.refreshToken }),
123
+ });
124
+
125
+ if (!response.ok) {
126
+ throw new Error(`Failed to refresh credentials: ${response.statusText}`);
127
+ }
128
+
129
+ const data = (await response.json()) as {
130
+ accessKeyId: string;
131
+ secretAccessKey: string;
132
+ sessionToken: string;
133
+ expiration: string;
134
+ };
135
+
136
+ const updated: Credentials = {
137
+ ...creds,
138
+ accessKeyId: data.accessKeyId,
139
+ secretAccessKey: data.secretAccessKey,
140
+ sessionToken: data.sessionToken,
141
+ expiration: data.expiration,
142
+ };
143
+
144
+ writeCredentials(updated);
145
+ return updated;
146
+ }