@indigoai-us/hq-cloud 5.1.0 → 5.1.9

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 (100) hide show
  1. package/dist/bin/sync-runner.d.ts +134 -0
  2. package/dist/bin/sync-runner.d.ts.map +1 -0
  3. package/dist/bin/sync-runner.js +360 -0
  4. package/dist/bin/sync-runner.js.map +1 -0
  5. package/dist/bin/sync-runner.test.d.ts +10 -0
  6. package/dist/bin/sync-runner.test.d.ts.map +1 -0
  7. package/dist/bin/sync-runner.test.js +648 -0
  8. package/dist/bin/sync-runner.test.js.map +1 -0
  9. package/dist/cli/index.d.ts +1 -1
  10. package/dist/cli/index.d.ts.map +1 -1
  11. package/dist/cli/share.js +2 -2
  12. package/dist/cli/share.js.map +1 -1
  13. package/dist/cli/share.test.js +9 -1
  14. package/dist/cli/share.test.js.map +1 -1
  15. package/dist/cli/sync.d.ts +28 -0
  16. package/dist/cli/sync.d.ts.map +1 -1
  17. package/dist/cli/sync.js +33 -10
  18. package/dist/cli/sync.js.map +1 -1
  19. package/dist/cli/sync.test.js +15 -4
  20. package/dist/cli/sync.test.js.map +1 -1
  21. package/dist/cognito-auth.d.ts.map +1 -1
  22. package/dist/cognito-auth.js +19 -1
  23. package/dist/cognito-auth.js.map +1 -1
  24. package/dist/cognito-auth.test.d.ts +9 -0
  25. package/dist/cognito-auth.test.d.ts.map +1 -0
  26. package/dist/cognito-auth.test.js +113 -0
  27. package/dist/cognito-auth.test.js.map +1 -0
  28. package/dist/context.d.ts.map +1 -1
  29. package/dist/context.js +1 -0
  30. package/dist/context.js.map +1 -1
  31. package/dist/daemon-worker.d.ts +6 -1
  32. package/dist/daemon-worker.d.ts.map +1 -1
  33. package/dist/daemon-worker.js +12 -16
  34. package/dist/daemon-worker.js.map +1 -1
  35. package/dist/daemon.d.ts +2 -0
  36. package/dist/daemon.d.ts.map +1 -1
  37. package/dist/daemon.js +2 -0
  38. package/dist/daemon.js.map +1 -1
  39. package/dist/ignore.d.ts +13 -2
  40. package/dist/ignore.d.ts.map +1 -1
  41. package/dist/ignore.js +69 -12
  42. package/dist/ignore.js.map +1 -1
  43. package/dist/index.d.ts +24 -28
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +19 -134
  46. package/dist/index.js.map +1 -1
  47. package/dist/journal.d.ts +20 -4
  48. package/dist/journal.d.ts.map +1 -1
  49. package/dist/journal.js +45 -8
  50. package/dist/journal.js.map +1 -1
  51. package/dist/journal.test.d.ts +9 -0
  52. package/dist/journal.test.d.ts.map +1 -0
  53. package/dist/journal.test.js +114 -0
  54. package/dist/journal.test.js.map +1 -0
  55. package/dist/s3.d.ts +18 -6
  56. package/dist/s3.d.ts.map +1 -1
  57. package/dist/s3.js +57 -56
  58. package/dist/s3.js.map +1 -1
  59. package/dist/types.d.ts +34 -0
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/vault-client.d.ts +59 -0
  62. package/dist/vault-client.d.ts.map +1 -1
  63. package/dist/vault-client.js +72 -0
  64. package/dist/vault-client.js.map +1 -1
  65. package/dist/vault-client.test.js +160 -0
  66. package/dist/vault-client.test.js.map +1 -1
  67. package/dist/watcher.d.ts +7 -1
  68. package/dist/watcher.d.ts.map +1 -1
  69. package/dist/watcher.js +11 -5
  70. package/dist/watcher.js.map +1 -1
  71. package/package.json +15 -3
  72. package/src/bin/sync-runner.test.ts +804 -0
  73. package/src/bin/sync-runner.ts +499 -0
  74. package/src/cli/accept.ts +97 -0
  75. package/src/cli/conflict.ts +119 -0
  76. package/src/cli/index.ts +25 -0
  77. package/src/cli/invite.test.ts +247 -0
  78. package/src/cli/invite.ts +180 -0
  79. package/src/cli/promote.ts +123 -0
  80. package/src/cli/share.test.ts +155 -0
  81. package/src/cli/share.ts +212 -0
  82. package/src/cli/sync.test.ts +225 -0
  83. package/src/cli/sync.ts +225 -0
  84. package/src/cognito-auth.test.ts +156 -0
  85. package/src/cognito-auth.ts +18 -1
  86. package/src/context.test.ts +202 -0
  87. package/src/context.ts +178 -0
  88. package/src/daemon-worker.ts +13 -19
  89. package/src/daemon.ts +2 -0
  90. package/src/ignore.ts +76 -12
  91. package/src/index.ts +94 -165
  92. package/src/journal.test.ts +146 -0
  93. package/src/journal.ts +53 -11
  94. package/src/s3.ts +76 -66
  95. package/src/types.ts +37 -0
  96. package/src/vault-client.test.ts +563 -0
  97. package/src/vault-client.ts +478 -0
  98. package/src/watcher.ts +12 -5
  99. package/test/invite-flow.integration.test.ts +244 -0
  100. package/test/share-sync.integration.test.ts +210 -0
package/dist/index.js CHANGED
@@ -1,138 +1,23 @@
1
1
  /**
2
2
  * @indigoai-us/hq-cloud — public API
3
- * Used by @indigoai-us/hq-cli to manage cloud sync
3
+ *
4
+ * VLT-5: Entity-aware sync engine. Operations resolve their target bucket
5
+ * and credentials from the vault-service entity registry + STS vending.
4
6
  */
5
- import * as fs from "fs";
6
- import * as path from "path";
7
- import { authenticate, hasCredentials, readCredentials } from "./auth.js";
8
- import { startDaemon as _startDaemon, stopDaemon as _stopDaemon, isDaemonRunning, } from "./daemon.js";
9
- import { readJournal, writeJournal, hashFile, updateEntry } from "./journal.js";
10
- import { uploadFile, downloadFile, listRemoteFiles } from "./s3.js";
11
- import { createIgnoreFilter, isWithinSizeLimit } from "./ignore.js";
12
- // Cognito identity helpers used by `hq auth refresh` and any consumer
13
- // that needs a valid HQ access token (deploy skill, onboarding, etc.).
14
- export { browserLogin, refreshTokens, getValidAccessToken, loadCachedTokens, saveCachedTokens, clearCachedTokens, isExpiring, CognitoAuthError, } from "./cognito-auth.js";
15
- /**
16
- * Initialize cloud sync authenticate and provision bucket
17
- */
18
- export async function initSync(hqRoot) {
19
- if (hasCredentials()) {
20
- console.log(" Already authenticated. Use 'hq sync start' to begin syncing.");
21
- return;
22
- }
23
- console.log(" Setting up IndigoAI cloud sync...");
24
- const creds = await authenticate();
25
- console.log(` ✓ Authenticated as ${creds.userId}`);
26
- console.log(` ✓ Bucket: ${creds.bucket}`);
27
- console.log(` ✓ Region: ${creds.region}`);
28
- console.log();
29
- console.log(" Run 'hq sync start' to begin syncing.");
30
- }
31
- /**
32
- * Start the background sync daemon
33
- */
34
- export async function startDaemon(hqRoot) {
35
- if (!hasCredentials()) {
36
- throw new Error("Not authenticated. Run 'hq sync init' first.");
37
- }
38
- _startDaemon(hqRoot);
39
- }
40
- /**
41
- * Stop the background sync daemon
42
- */
43
- export async function stopDaemon(hqRoot) {
44
- _stopDaemon(hqRoot);
45
- }
46
- /**
47
- * Get current sync status
48
- */
49
- export async function getStatus(hqRoot) {
50
- const journal = readJournal(hqRoot);
51
- const creds = readCredentials();
52
- const running = isDaemonRunning(hqRoot);
53
- const errors = [];
54
- if (!creds) {
55
- errors.push("Not authenticated — run 'hq sync init'");
56
- }
57
- return {
58
- running,
59
- lastSync: journal.lastSync || null,
60
- fileCount: Object.keys(journal.files).length,
61
- bucket: creds?.bucket || null,
62
- errors,
63
- };
64
- }
65
- /**
66
- * Force push all local files to S3
67
- */
68
- export async function pushAll(hqRoot) {
69
- const shouldSync = createIgnoreFilter(hqRoot);
70
- const journal = readJournal(hqRoot);
71
- let filesUploaded = 0;
72
- let bytesUploaded = 0;
73
- const files = walkDir(hqRoot, hqRoot, shouldSync);
74
- for (const { absolutePath, relativePath } of files) {
75
- if (!isWithinSizeLimit(absolutePath))
76
- continue;
77
- try {
78
- const hash = hashFile(absolutePath);
79
- const stat = fs.statSync(absolutePath);
80
- await uploadFile(absolutePath, relativePath);
81
- updateEntry(journal, relativePath, hash, stat.size, "up");
82
- filesUploaded++;
83
- bytesUploaded += stat.size;
84
- }
85
- catch (err) {
86
- console.error(` Failed: ${relativePath} — ${err instanceof Error ? err.message : err}`);
87
- }
88
- }
89
- writeJournal(hqRoot, journal);
90
- return { filesUploaded, bytesUploaded };
91
- }
92
- /**
93
- * Force pull all remote files to local
94
- */
95
- export async function pullAll(hqRoot) {
96
- const journal = readJournal(hqRoot);
97
- let filesDownloaded = 0;
98
- let bytesDownloaded = 0;
99
- const remoteFiles = await listRemoteFiles();
100
- for (const file of remoteFiles) {
101
- try {
102
- const localPath = path.join(hqRoot, file.relativePath);
103
- await downloadFile(file.relativePath, localPath);
104
- const hash = hashFile(localPath);
105
- updateEntry(journal, file.relativePath, hash, file.size, "down");
106
- filesDownloaded++;
107
- bytesDownloaded += file.size;
108
- }
109
- catch (err) {
110
- console.error(` Failed: ${file.relativePath} — ${err instanceof Error ? err.message : err}`);
111
- }
112
- }
113
- writeJournal(hqRoot, journal);
114
- return { filesDownloaded, bytesDownloaded };
115
- }
116
- // Helper: recursively walk a directory
117
- function walkDir(dir, root, filter) {
118
- const results = [];
119
- if (!fs.existsSync(dir))
120
- return results;
121
- const entries = fs.readdirSync(dir, { withFileTypes: true });
122
- for (const entry of entries) {
123
- const absolutePath = path.join(dir, entry.name);
124
- if (!filter(absolutePath))
125
- continue;
126
- if (entry.isDirectory()) {
127
- results.push(...walkDir(absolutePath, root, filter));
128
- }
129
- else if (entry.isFile()) {
130
- results.push({
131
- absolutePath,
132
- relativePath: path.relative(root, absolutePath),
133
- });
134
- }
135
- }
136
- return results;
137
- }
7
+ export { resolveEntityContext, refreshEntityContext, clearContextCache, isExpiringSoon, } from "./context.js";
8
+ export { uploadFile, downloadFile, listRemoteFiles, deleteRemoteFile, headRemoteFile, } from "./s3.js";
9
+ export { readJournal, writeJournal, hashFile, updateEntry, getEntry, removeEntry, getJournalPath, } from "./journal.js";
10
+ export { createIgnoreFilter, isWithinSizeLimit, } from "./ignore.js";
11
+ // Cognito browser-OAuth (VLT-9)
12
+ export { browserLogin, refreshTokens, loadCachedTokens, saveCachedTokens, clearCachedTokens, isExpiring, getValidAccessToken, CognitoAuthError, } from "./cognito-auth.js";
13
+ // VaultClient SDK (VLT-7)
14
+ export { VaultClient } from "./vault-client.js";
15
+ export { VaultClientError, VaultAuthError, VaultPermissionDeniedError, VaultNotFoundError, VaultConflictError, } from "./vault-client.js";
16
+ // CLI commands
17
+ export { share, sync } from "./cli/index.js";
18
+ export { resolveConflict, showDiff } from "./cli/index.js";
19
+ // Membership CLI commands (VLT-7)
20
+ export { invite, listInvites, revokeInvite } from "./cli/index.js";
21
+ export { accept, parseToken } from "./cli/index.js";
22
+ export { promote } from "./cli/index.js";
138
23
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,UAAU,EACV,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,cAAc,GACf,MAAM,SAAS,CAAC;AAIjB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,WAAW,EACX,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,gCAAgC;AAChC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAG3B,0BAA0B;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAwB3B,eAAe;AACf,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG3D,kCAAkC;AAClC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/journal.d.ts CHANGED
@@ -1,10 +1,26 @@
1
1
  /**
2
- * Sync journal — tracks file state for conflict detection
2
+ * Sync journal — tracks per-file state (hash, size, last-synced direction) so
3
+ * sync/share can detect local edits that would be clobbered by a blind pull.
4
+ *
5
+ * ADR-0001 Phase 5: the journal is sharded by company slug and lives in
6
+ * `~/.hq/`, not inside the HQ content root. One monolithic journal per HQ
7
+ * install conflates state across companies and forces every runner to
8
+ * serialize through the same file — splitting it lets `hq-sync-runner
9
+ * --companies` fan out without contention, and a corrupted shard only affects
10
+ * one company.
11
+ *
12
+ * Path: `{stateDir}/sync-journal.{slug}.json`, where `stateDir` resolves to
13
+ * `HQ_STATE_DIR` (if set) or `~/.hq`.
3
14
  */
4
15
  import type { SyncJournal, JournalEntry } from "./types.js";
5
- export declare function getJournalPath(hqRoot: string): string;
6
- export declare function readJournal(hqRoot: string): SyncJournal;
7
- export declare function writeJournal(hqRoot: string, journal: SyncJournal): void;
16
+ /**
17
+ * Where per-company journals are stored. Honors `HQ_STATE_DIR` for tests and
18
+ * non-standard installs; otherwise falls back to `~/.hq`.
19
+ */
20
+ export declare function getStateDir(): string;
21
+ export declare function getJournalPath(slug: string): string;
22
+ export declare function readJournal(slug: string): SyncJournal;
23
+ export declare function writeJournal(slug: string, journal: SyncJournal): void;
8
24
  export declare function hashFile(filePath: string): string;
9
25
  export declare function updateEntry(journal: SyncJournal, relativePath: string, hash: string, size: number, direction: "up" | "down"): void;
10
26
  export declare function getEntry(journal: SyncJournal, relativePath: string): JournalEntry | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI5D,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAOvD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAGvE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,IAAI,GAAG,MAAM,GACvB,IAAI,CAQN;AAED,wBAAgB,QAAQ,CACtB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,YAAY,GAAG,SAAS,CAE1B;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,IAAI,CAEN"}
1
+ {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK5D;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAmBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAOrD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAIrE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,IAAI,GAAG,MAAM,GACvB,IAAI,CAQN;AAED,wBAAgB,QAAQ,CACtB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,YAAY,GAAG,SAAS,CAE1B;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,IAAI,CAEN"}
package/dist/journal.js CHANGED
@@ -1,23 +1,60 @@
1
1
  /**
2
- * Sync journal — tracks file state for conflict detection
2
+ * Sync journal — tracks per-file state (hash, size, last-synced direction) so
3
+ * sync/share can detect local edits that would be clobbered by a blind pull.
4
+ *
5
+ * ADR-0001 Phase 5: the journal is sharded by company slug and lives in
6
+ * `~/.hq/`, not inside the HQ content root. One monolithic journal per HQ
7
+ * install conflates state across companies and forces every runner to
8
+ * serialize through the same file — splitting it lets `hq-sync-runner
9
+ * --companies` fan out without contention, and a corrupted shard only affects
10
+ * one company.
11
+ *
12
+ * Path: `{stateDir}/sync-journal.{slug}.json`, where `stateDir` resolves to
13
+ * `HQ_STATE_DIR` (if set) or `~/.hq`.
3
14
  */
4
15
  import * as fs from "fs";
16
+ import * as os from "os";
5
17
  import * as path from "path";
6
18
  import * as crypto from "crypto";
7
- const JOURNAL_FILE = ".hq-sync-journal.json";
8
- export function getJournalPath(hqRoot) {
9
- return path.join(hqRoot, JOURNAL_FILE);
19
+ const JOURNAL_FILE_PREFIX = "sync-journal.";
20
+ const JOURNAL_FILE_SUFFIX = ".json";
21
+ /**
22
+ * Where per-company journals are stored. Honors `HQ_STATE_DIR` for tests and
23
+ * non-standard installs; otherwise falls back to `~/.hq`.
24
+ */
25
+ export function getStateDir() {
26
+ return process.env.HQ_STATE_DIR ?? path.join(os.homedir(), ".hq");
27
+ }
28
+ /**
29
+ * Filename-safe form of a slug. Slugs from vault-service are already
30
+ * URL-safe, but this guards against paths, dots, or anything the filesystem
31
+ * might interpret. Empty-or-invalid slugs throw rather than silently writing
32
+ * to a shared "sync-journal..json" file.
33
+ */
34
+ function sanitizeSlug(slug) {
35
+ if (!slug) {
36
+ throw new Error("journal: slug is required (empty or undefined)");
37
+ }
38
+ const cleaned = slug.replace(/[^a-zA-Z0-9_-]/g, "_");
39
+ if (!cleaned || /^[_-]+$/.test(cleaned)) {
40
+ throw new Error(`journal: slug "${slug}" sanitizes to an empty identifier`);
41
+ }
42
+ return cleaned;
43
+ }
44
+ export function getJournalPath(slug) {
45
+ return path.join(getStateDir(), `${JOURNAL_FILE_PREFIX}${sanitizeSlug(slug)}${JOURNAL_FILE_SUFFIX}`);
10
46
  }
11
- export function readJournal(hqRoot) {
12
- const journalPath = getJournalPath(hqRoot);
47
+ export function readJournal(slug) {
48
+ const journalPath = getJournalPath(slug);
13
49
  if (fs.existsSync(journalPath)) {
14
50
  const content = fs.readFileSync(journalPath, "utf-8");
15
51
  return JSON.parse(content);
16
52
  }
17
53
  return { version: "1", lastSync: "", files: {} };
18
54
  }
19
- export function writeJournal(hqRoot, journal) {
20
- const journalPath = getJournalPath(hqRoot);
55
+ export function writeJournal(slug, journal) {
56
+ const journalPath = getJournalPath(slug);
57
+ fs.mkdirSync(path.dirname(journalPath), { recursive: true });
21
58
  fs.writeFileSync(journalPath, JSON.stringify(journal, null, 2));
22
59
  }
23
60
  export function hashFile(filePath) {
@@ -1 +1 @@
1
- {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,MAAM,YAAY,GAAG,uBAAuB,CAAC;AAE7C,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,OAAoB;IAC/D,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3C,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAAoB,EACpB,YAAoB,EACpB,IAAY,EACZ,IAAY,EACZ,SAAwB;IAExB,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG;QAC5B,IAAI;QACJ,IAAI;QACJ,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAClC,SAAS;KACV,CAAC;IACF,OAAO,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,OAAoB,EACpB,YAAoB;IAEpB,OAAO,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAAoB,EACpB,YAAoB;IAEpB,OAAO,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for the sync journal (ADR-0001 Phase 5).
3
+ *
4
+ * Verifies per-company isolation, HQ_STATE_DIR override, and filename
5
+ * sanitization — all behaviors that the pre-Phase-5 monolithic journal
6
+ * didn't need.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=journal.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.test.d.ts","sourceRoot":"","sources":["../src/journal.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Unit tests for the sync journal (ADR-0001 Phase 5).
3
+ *
4
+ * Verifies per-company isolation, HQ_STATE_DIR override, and filename
5
+ * sanitization — all behaviors that the pre-Phase-5 monolithic journal
6
+ * didn't need.
7
+ */
8
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
9
+ import * as fs from "fs";
10
+ import * as os from "os";
11
+ import * as path from "path";
12
+ import { getJournalPath, getStateDir, readJournal, writeJournal, updateEntry, } from "./journal.js";
13
+ describe("journal", () => {
14
+ let stateDir;
15
+ beforeEach(() => {
16
+ stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-journal-test-"));
17
+ process.env.HQ_STATE_DIR = stateDir;
18
+ });
19
+ afterEach(() => {
20
+ fs.rmSync(stateDir, { recursive: true, force: true });
21
+ delete process.env.HQ_STATE_DIR;
22
+ });
23
+ describe("getStateDir", () => {
24
+ it("honors HQ_STATE_DIR env var", () => {
25
+ expect(getStateDir()).toBe(stateDir);
26
+ });
27
+ it("falls back to ~/.hq when env var unset", () => {
28
+ delete process.env.HQ_STATE_DIR;
29
+ expect(getStateDir()).toBe(path.join(os.homedir(), ".hq"));
30
+ });
31
+ });
32
+ describe("getJournalPath", () => {
33
+ it("produces a per-slug filename", () => {
34
+ expect(getJournalPath("indigo")).toBe(path.join(stateDir, "sync-journal.indigo.json"));
35
+ });
36
+ it("isolates different slugs into different files", () => {
37
+ expect(getJournalPath("indigo")).not.toBe(getJournalPath("brandstage"));
38
+ });
39
+ it("sanitizes path-unsafe characters", () => {
40
+ expect(getJournalPath("foo/bar")).toBe(path.join(stateDir, "sync-journal.foo_bar.json"));
41
+ expect(getJournalPath("../escape")).toBe(path.join(stateDir, "sync-journal.___escape.json"));
42
+ });
43
+ it("throws on empty slug", () => {
44
+ expect(() => getJournalPath("")).toThrow(/slug is required/);
45
+ });
46
+ it("throws on slug that sanitizes to empty", () => {
47
+ expect(() => getJournalPath("///")).toThrow(/empty identifier/);
48
+ });
49
+ });
50
+ describe("readJournal", () => {
51
+ it("returns an empty journal when the file doesn't exist", () => {
52
+ const j = readJournal("indigo");
53
+ expect(j.version).toBe("1");
54
+ expect(j.files).toEqual({});
55
+ expect(j.lastSync).toBe("");
56
+ });
57
+ it("reads a journal written with writeJournal", () => {
58
+ const original = {
59
+ version: "1",
60
+ lastSync: "2026-04-19T00:00:00.000Z",
61
+ files: {
62
+ "docs/handoff.md": {
63
+ hash: "abc123",
64
+ size: 42,
65
+ syncedAt: "2026-04-19T00:00:00.000Z",
66
+ direction: "down",
67
+ },
68
+ },
69
+ };
70
+ writeJournal("indigo", original);
71
+ const roundTripped = readJournal("indigo");
72
+ expect(roundTripped).toEqual(original);
73
+ });
74
+ });
75
+ describe("writeJournal", () => {
76
+ it("creates the state directory if it doesn't exist", () => {
77
+ const nestedDir = path.join(stateDir, "nested", "deep");
78
+ process.env.HQ_STATE_DIR = nestedDir;
79
+ expect(fs.existsSync(nestedDir)).toBe(false);
80
+ writeJournal("indigo", { version: "1", lastSync: "", files: {} });
81
+ expect(fs.existsSync(nestedDir)).toBe(true);
82
+ expect(fs.existsSync(path.join(nestedDir, "sync-journal.indigo.json"))).toBe(true);
83
+ });
84
+ it("keeps per-company journals independent", () => {
85
+ writeJournal("indigo", {
86
+ version: "1",
87
+ lastSync: "",
88
+ files: { "a.md": { hash: "1", size: 1, syncedAt: "", direction: "up" } },
89
+ });
90
+ writeJournal("brandstage", {
91
+ version: "1",
92
+ lastSync: "",
93
+ files: { "b.md": { hash: "2", size: 2, syncedAt: "", direction: "up" } },
94
+ });
95
+ const indigo = readJournal("indigo");
96
+ const brandstage = readJournal("brandstage");
97
+ expect(indigo.files).toHaveProperty("a.md");
98
+ expect(indigo.files).not.toHaveProperty("b.md");
99
+ expect(brandstage.files).toHaveProperty("b.md");
100
+ expect(brandstage.files).not.toHaveProperty("a.md");
101
+ });
102
+ });
103
+ describe("updateEntry", () => {
104
+ it("stamps lastSync and the per-file syncedAt", () => {
105
+ const j = { version: "1", lastSync: "", files: {} };
106
+ updateEntry(j, "foo.md", "hash", 10, "up");
107
+ expect(j.files["foo.md"]?.hash).toBe("hash");
108
+ expect(j.files["foo.md"]?.direction).toBe("up");
109
+ expect(j.lastSync).not.toBe("");
110
+ expect(j.files["foo.md"]?.syncedAt).not.toBe("");
111
+ });
112
+ });
113
+ });
114
+ //# sourceMappingURL=journal.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.test.js","sourceRoot":"","sources":["../src/journal.test.ts"],"names":[],"mappings":"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"}
package/dist/s3.d.ts CHANGED
@@ -1,15 +1,27 @@
1
1
  /**
2
- * S3 operations — upload, download, list, delete
2
+ * S3 operations — upload, download, list, delete.
3
+ *
4
+ * VLT-5: All operations now accept an EntityContext (entity-aware bucket +
5
+ * STS-scoped credentials) instead of reading static env config. The caller
6
+ * is responsible for resolving the context via resolveEntityContext().
3
7
  */
4
- export declare function uploadFile(localPath: string, relativePath: string): Promise<void>;
5
- export declare function downloadFile(relativePath: string, localPath: string): Promise<void>;
8
+ import type { EntityContext } from "./types.js";
9
+ export declare function uploadFile(ctx: EntityContext, localPath: string, key: string): Promise<void>;
10
+ export declare function downloadFile(ctx: EntityContext, key: string, localPath: string): Promise<void>;
6
11
  export interface RemoteFile {
7
12
  key: string;
8
- relativePath: string;
9
13
  size: number;
10
14
  lastModified: Date;
11
15
  etag: string;
12
16
  }
13
- export declare function listRemoteFiles(): Promise<RemoteFile[]>;
14
- export declare function deleteRemoteFile(relativePath: string): Promise<void>;
17
+ export declare function listRemoteFiles(ctx: EntityContext, prefix?: string): Promise<RemoteFile[]>;
18
+ export declare function deleteRemoteFile(ctx: EntityContext, key: string): Promise<void>;
19
+ /**
20
+ * Check if a remote key exists and return its metadata.
21
+ */
22
+ export declare function headRemoteFile(ctx: EntityContext, key: string): Promise<{
23
+ lastModified: Date;
24
+ etag: string;
25
+ size: number;
26
+ } | null>;
15
27
  //# sourceMappingURL=s3.d.ts.map
package/dist/s3.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"AAAA;;GAEG;AAqDH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAgC7D;AAED,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU1E"}
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAkBhD,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,aAAa,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoBpE"}
package/dist/s3.js CHANGED
@@ -1,59 +1,42 @@
1
1
  /**
2
- * S3 operations — upload, download, list, delete
2
+ * S3 operations — upload, download, list, delete.
3
+ *
4
+ * VLT-5: All operations now accept an EntityContext (entity-aware bucket +
5
+ * STS-scoped credentials) instead of reading static env config. The caller
6
+ * is responsible for resolving the context via resolveEntityContext().
3
7
  */
4
8
  import * as fs from "fs";
5
9
  import * as path from "path";
6
- import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand, } from "@aws-sdk/client-s3";
7
- import { readCredentials, refreshAwsCredentials } from "./auth.js";
8
- let s3Client = null;
9
- function getConfig(creds) {
10
- const prefix = creds.teamId
11
- ? `teams/${creds.teamId}/users/${creds.userId}/hq/`
12
- : `users/${creds.userId}/hq/`;
13
- return {
14
- bucket: creds.bucket,
15
- region: creds.region,
16
- userId: creds.userId,
17
- prefix,
18
- };
19
- }
20
- async function getClient() {
21
- let creds = readCredentials();
22
- if (!creds) {
23
- throw new Error("Not authenticated. Run 'hq sync init' first.");
24
- }
25
- // Refresh if expired or missing access key
26
- if (!creds.accessKeyId || (creds.expiration && new Date(creds.expiration) < new Date())) {
27
- creds = await refreshAwsCredentials(creds);
28
- }
29
- if (!s3Client) {
30
- s3Client = new S3Client({
31
- region: creds.region,
32
- credentials: {
33
- accessKeyId: creds.accessKeyId,
34
- secretAccessKey: creds.secretAccessKey,
35
- sessionToken: creds.sessionToken,
36
- },
37
- });
38
- }
39
- return { client: s3Client, config: getConfig(creds) };
10
+ import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand, HeadObjectCommand, } from "@aws-sdk/client-s3";
11
+ /**
12
+ * Build an S3Client from an EntityContext's STS-scoped credentials.
13
+ * A new client is created each time to ensure fresh credentials are used
14
+ * (the caller handles caching/refresh at the EntityContext level).
15
+ */
16
+ function buildClient(ctx) {
17
+ return new S3Client({
18
+ region: ctx.region,
19
+ credentials: {
20
+ accessKeyId: ctx.credentials.accessKeyId,
21
+ secretAccessKey: ctx.credentials.secretAccessKey,
22
+ sessionToken: ctx.credentials.sessionToken,
23
+ },
24
+ });
40
25
  }
41
- export async function uploadFile(localPath, relativePath) {
42
- const { client, config } = await getClient();
43
- const key = `${config.prefix}${relativePath}`;
26
+ export async function uploadFile(ctx, localPath, key) {
27
+ const client = buildClient(ctx);
44
28
  const body = fs.readFileSync(localPath);
45
29
  await client.send(new PutObjectCommand({
46
- Bucket: config.bucket,
30
+ Bucket: ctx.bucketName,
47
31
  Key: key,
48
32
  Body: body,
49
- ContentType: getMimeType(relativePath),
33
+ ContentType: getMimeType(key),
50
34
  }));
51
35
  }
52
- export async function downloadFile(relativePath, localPath) {
53
- const { client, config } = await getClient();
54
- const key = `${config.prefix}${relativePath}`;
36
+ export async function downloadFile(ctx, key, localPath) {
37
+ const client = buildClient(ctx);
55
38
  const response = await client.send(new GetObjectCommand({
56
- Bucket: config.bucket,
39
+ Bucket: ctx.bucketName,
57
40
  Key: key,
58
41
  }));
59
42
  if (!response.Body) {
@@ -70,25 +53,21 @@ export async function downloadFile(relativePath, localPath) {
70
53
  }
71
54
  fs.writeFileSync(localPath, Buffer.concat(chunks));
72
55
  }
73
- export async function listRemoteFiles() {
74
- const { client, config } = await getClient();
56
+ export async function listRemoteFiles(ctx, prefix) {
57
+ const client = buildClient(ctx);
75
58
  const files = [];
76
59
  let continuationToken;
77
60
  do {
78
61
  const response = await client.send(new ListObjectsV2Command({
79
- Bucket: config.bucket,
80
- Prefix: config.prefix,
62
+ Bucket: ctx.bucketName,
63
+ Prefix: prefix,
81
64
  ContinuationToken: continuationToken,
82
65
  }));
83
66
  for (const obj of response.Contents || []) {
84
67
  if (!obj.Key || !obj.Size)
85
68
  continue;
86
- const relativePath = obj.Key.replace(config.prefix, "");
87
- if (!relativePath)
88
- continue;
89
69
  files.push({
90
70
  key: obj.Key,
91
- relativePath,
92
71
  size: obj.Size,
93
72
  lastModified: obj.LastModified || new Date(),
94
73
  etag: obj.ETag || "",
@@ -98,14 +77,36 @@ export async function listRemoteFiles() {
98
77
  } while (continuationToken);
99
78
  return files;
100
79
  }
101
- export async function deleteRemoteFile(relativePath) {
102
- const { client, config } = await getClient();
103
- const key = `${config.prefix}${relativePath}`;
80
+ export async function deleteRemoteFile(ctx, key) {
81
+ const client = buildClient(ctx);
104
82
  await client.send(new DeleteObjectCommand({
105
- Bucket: config.bucket,
83
+ Bucket: ctx.bucketName,
106
84
  Key: key,
107
85
  }));
108
86
  }
87
+ /**
88
+ * Check if a remote key exists and return its metadata.
89
+ */
90
+ export async function headRemoteFile(ctx, key) {
91
+ const client = buildClient(ctx);
92
+ try {
93
+ const response = await client.send(new HeadObjectCommand({
94
+ Bucket: ctx.bucketName,
95
+ Key: key,
96
+ }));
97
+ return {
98
+ lastModified: response.LastModified || new Date(),
99
+ etag: response.ETag || "",
100
+ size: response.ContentLength || 0,
101
+ };
102
+ }
103
+ catch (err) {
104
+ if (err && typeof err === "object" && "name" in err && err.name === "NotFound") {
105
+ return null;
106
+ }
107
+ throw err;
108
+ }
109
+ }
109
110
  function getMimeType(filePath) {
110
111
  const ext = path.extname(filePath).toLowerCase();
111
112
  const mimeTypes = {