@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/src/context.ts ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Entity context resolution (VLT-5 US-001).
3
+ *
4
+ * Resolves an entity (company) via vault-service, vends STS-scoped credentials,
5
+ * and returns an EntityContext for S3 operations. Handles auto-refresh when
6
+ * credentials are within 2 minutes of expiry.
7
+ */
8
+
9
+ import type { EntityContext, VaultServiceConfig } from "./types.js";
10
+
11
+ /** Minimum remaining TTL before auto-refresh triggers (2 minutes). */
12
+ const REFRESH_THRESHOLD_MS = 2 * 60 * 1000;
13
+
14
+ /** STS session duration requested from vault-service (15 minutes). */
15
+ const DEFAULT_SESSION_DURATION_SECONDS = 900;
16
+
17
+ /** Cached contexts keyed by entity UID. */
18
+ const contextCache = new Map<string, EntityContext>();
19
+
20
+ /**
21
+ * Look up an entity by slug or UID via vault-service, then vend STS-scoped
22
+ * credentials for that entity. Returns an EntityContext ready for S3 ops.
23
+ *
24
+ * Caches the result and auto-refreshes when the credentials are within
25
+ * 2 minutes of expiry.
26
+ */
27
+ export async function resolveEntityContext(
28
+ companyUidOrSlug: string,
29
+ config: VaultServiceConfig,
30
+ ): Promise<EntityContext> {
31
+ // Check cache — return if credentials still fresh
32
+ const cached = contextCache.get(companyUidOrSlug);
33
+ if (cached && !isExpiringSoon(cached.expiresAt)) {
34
+ return cached;
35
+ }
36
+
37
+ // Step 1: Resolve entity — if it looks like a UID (cmp_*), fetch directly;
38
+ // otherwise look up by slug
39
+ const entity = companyUidOrSlug.startsWith("cmp_")
40
+ ? await fetchEntity(companyUidOrSlug, config)
41
+ : await fetchEntityBySlug("company", companyUidOrSlug, config);
42
+
43
+ if (!entity.bucketName) {
44
+ throw new Error(
45
+ `Entity ${entity.uid} (${entity.slug}) has no bucket provisioned. ` +
46
+ `Run VLT-2 bucket provisioning first.`,
47
+ );
48
+ }
49
+
50
+ // Step 2: Vend STS-scoped credentials
51
+ const vendResult = await vendCredentials(entity.uid, config);
52
+
53
+ const ctx: EntityContext = {
54
+ uid: entity.uid,
55
+ slug: entity.slug,
56
+ bucketName: entity.bucketName,
57
+ region: config.region ?? "us-east-1",
58
+ credentials: {
59
+ accessKeyId: vendResult.credentials.accessKeyId,
60
+ secretAccessKey: vendResult.credentials.secretAccessKey,
61
+ sessionToken: vendResult.credentials.sessionToken,
62
+ },
63
+ expiresAt: vendResult.expiresAt,
64
+ };
65
+
66
+ // Cache by both UID and slug for fast lookups
67
+ contextCache.set(entity.uid, ctx);
68
+ contextCache.set(entity.slug, ctx);
69
+
70
+ return ctx;
71
+ }
72
+
73
+ /**
74
+ * Check if credentials are expiring within the refresh threshold.
75
+ */
76
+ export function isExpiringSoon(expiresAt: string): boolean {
77
+ const expiryMs = new Date(expiresAt).getTime();
78
+ const nowMs = Date.now();
79
+ return expiryMs - nowMs < REFRESH_THRESHOLD_MS;
80
+ }
81
+
82
+ /**
83
+ * Force-refresh a cached context. Useful when an S3 operation fails with
84
+ * an expired credentials error.
85
+ */
86
+ export async function refreshEntityContext(
87
+ companyUidOrSlug: string,
88
+ config: VaultServiceConfig,
89
+ ): Promise<EntityContext> {
90
+ // Evict cache entry to force fresh resolution
91
+ contextCache.delete(companyUidOrSlug);
92
+ return resolveEntityContext(companyUidOrSlug, config);
93
+ }
94
+
95
+ /**
96
+ * Clear the entire context cache. Useful for tests.
97
+ */
98
+ export function clearContextCache(): void {
99
+ contextCache.clear();
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Vault-service API calls
104
+ // ---------------------------------------------------------------------------
105
+
106
+ interface EntityResponse {
107
+ uid: string;
108
+ slug: string;
109
+ bucketName?: string;
110
+ status: string;
111
+ }
112
+
113
+ interface VendResponse {
114
+ credentials: {
115
+ accessKeyId: string;
116
+ secretAccessKey: string;
117
+ sessionToken: string;
118
+ expiration: string;
119
+ };
120
+ expiresAt: string;
121
+ }
122
+
123
+ async function fetchEntity(
124
+ uid: string,
125
+ config: VaultServiceConfig,
126
+ ): Promise<EntityResponse> {
127
+ const res = await fetch(`${config.apiUrl}/entity/${uid}`, {
128
+ headers: { Authorization: `Bearer ${config.authToken}` },
129
+ });
130
+ if (!res.ok) {
131
+ const body = await res.text();
132
+ throw new Error(`Failed to fetch entity ${uid}: ${res.status} ${body}`);
133
+ }
134
+ const data = (await res.json()) as { entity: EntityResponse };
135
+ return data.entity;
136
+ }
137
+
138
+ async function fetchEntityBySlug(
139
+ type: string,
140
+ slug: string,
141
+ config: VaultServiceConfig,
142
+ ): Promise<EntityResponse> {
143
+ const res = await fetch(`${config.apiUrl}/entity/by-slug/${type}/${slug}`, {
144
+ headers: { Authorization: `Bearer ${config.authToken}` },
145
+ });
146
+ if (!res.ok) {
147
+ const body = await res.text();
148
+ throw new Error(
149
+ `Failed to find entity by slug ${type}/${slug}: ${res.status} ${body}`,
150
+ );
151
+ }
152
+ const data = (await res.json()) as { entity: EntityResponse };
153
+ return data.entity;
154
+ }
155
+
156
+ async function vendCredentials(
157
+ companyUid: string,
158
+ config: VaultServiceConfig,
159
+ ): Promise<VendResponse> {
160
+ const res = await fetch(`${config.apiUrl}/sts/vend`, {
161
+ method: "POST",
162
+ headers: {
163
+ "Content-Type": "application/json",
164
+ Authorization: `Bearer ${config.authToken}`,
165
+ },
166
+ body: JSON.stringify({
167
+ companyUid,
168
+ durationSeconds: DEFAULT_SESSION_DURATION_SECONDS,
169
+ }),
170
+ });
171
+ if (!res.ok) {
172
+ const body = await res.text();
173
+ throw new Error(
174
+ `STS vend failed for ${companyUid}: ${res.status} ${body}`,
175
+ );
176
+ }
177
+ return (await res.json()) as VendResponse;
178
+ }
@@ -1,9 +1,16 @@
1
1
  /**
2
2
  * Daemon worker — runs as a detached child process
3
3
  * Watches HQ directory and syncs changes to S3
4
+ *
5
+ * Day 1: not invoked by CLI surface; retained for future automatic-sync milestone.
6
+ * When re-enabled, this worker will need to resolve an EntityContext before
7
+ * constructing the SyncWatcher. The process argv will need to include company
8
+ * context (slug or UID) and vault-service config.
4
9
  */
5
10
 
6
- import { SyncWatcher } from "./watcher.js";
11
+ // Day 1: SyncWatcher now requires an EntityContext.
12
+ // This file is retained for the automatic-sync milestone but is not functional
13
+ // until the daemon startup path is updated to resolve entity context.
7
14
 
8
15
  const hqRoot = process.argv[2];
9
16
 
@@ -12,21 +19,8 @@ if (!hqRoot) {
12
19
  process.exit(1);
13
20
  }
14
21
 
15
- const watcher = new SyncWatcher(hqRoot);
16
- watcher.start();
17
-
18
- // Handle graceful shutdown
19
- process.on("SIGTERM", () => {
20
- watcher.stop();
21
- process.exit(0);
22
- });
23
-
24
- process.on("SIGINT", () => {
25
- watcher.stop();
26
- process.exit(0);
27
- });
28
-
29
- // Keep process alive
30
- setInterval(() => {
31
- // Heartbeat — could add remote change polling here
32
- }, 30_000);
22
+ console.error(
23
+ "Day 1: daemon-worker is not yet wired to entity context resolution. " +
24
+ "Use 'hq share' and 'hq sync' for manual sync.",
25
+ );
26
+ process.exit(1);
package/src/daemon.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Background sync daemon management
3
3
  * Manages a child process that runs the file watcher
4
+ *
5
+ * Day 1: not invoked by CLI surface; retained for future automatic-sync milestone.
4
6
  */
5
7
 
6
8
  import * as fs from "fs";
package/src/ignore.ts CHANGED
@@ -1,42 +1,106 @@
1
1
  /**
2
- * Ignore file parser for .hqsyncignore
3
- * Uses gitignore-compatible syntax
2
+ * Ignore-file parser for cloud sync.
3
+ *
4
+ * Three layers, evaluated in order (later patterns override earlier ones):
5
+ * 1. Built-in defaults — things that should *never* sync (VCS, node_modules,
6
+ * build artifacts, caches, env files). Cover the common stacks so that a
7
+ * first-time sync over a random project folder doesn't try to push
8
+ * `target/`, `node_modules/`, or `.next/` to S3.
9
+ * 2. Repo `.gitignore` at hqRoot — reuses the user's existing exclusions so
10
+ * we don't re-list every build directory ourselves. Root-level only; we
11
+ * do not recurse like real git.
12
+ * 3. `.hqignore` (preferred) or `.hqsyncignore` (legacy name) at hqRoot —
13
+ * sync-specific overrides. Use `!pattern` to re-include something an
14
+ * earlier layer excluded.
4
15
  */
5
16
 
6
17
  import * as fs from "fs";
7
18
  import * as path from "path";
8
19
  import ignore from "ignore";
9
20
 
10
- // Default patterns that should never sync
21
+ // Patterns that must never sync regardless of project type.
22
+ // Grouped by ecosystem so new stacks are easy to add.
11
23
  const DEFAULT_IGNORES = [
24
+ // VCS + OS
12
25
  ".git/",
13
26
  ".git",
14
- "node_modules/",
15
- "dist/",
16
27
  ".DS_Store",
17
28
  "Thumbs.db",
29
+
30
+ // Node / JS
31
+ "node_modules/",
32
+ "dist/",
33
+ "build/",
34
+ ".next/",
35
+ ".nuxt/",
36
+ ".svelte-kit/",
37
+ ".turbo/",
38
+ ".parcel-cache/",
39
+ ".vite/",
40
+ "coverage/",
41
+
42
+ // Rust / Tauri
43
+ "target/",
44
+
45
+ // Python
46
+ "__pycache__/",
47
+ "*.pyc",
48
+ ".pytest_cache/",
49
+ ".mypy_cache/",
50
+ ".ruff_cache/",
51
+ ".venv/",
52
+ "venv/",
53
+
54
+ // Go / JVM / other
55
+ "vendor/",
56
+ "out/",
57
+ "*.class",
58
+
59
+ // Generic caches / temp
60
+ ".cache/",
61
+ "tmp/",
62
+ ".tmp/",
63
+
64
+ // HQ sync internal state (never round-trip these)
18
65
  "*.pid",
19
66
  ".hq-sync.pid",
20
67
  ".hq-sync-journal.json",
21
68
  ".hq-sync-state.json",
22
69
  "modules.lock",
70
+
71
+ // HQ repos directory (managed separately, not synced)
23
72
  "repos/",
73
+
74
+ // Secrets / env
24
75
  ".env",
25
76
  ".env.*",
26
77
  ];
27
78
 
79
+ function readIgnoreFile(filePath: string): string | null {
80
+ if (!fs.existsSync(filePath)) return null;
81
+ try {
82
+ return fs.readFileSync(filePath, "utf-8");
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
28
88
  export function createIgnoreFilter(hqRoot: string): (filePath: string) => boolean {
29
89
  const ig = ignore();
30
90
 
31
- // Add defaults
91
+ // Layer 1: baseline defaults
32
92
  ig.add(DEFAULT_IGNORES);
33
93
 
34
- // Read .hqsyncignore if it exists
35
- const ignorePath = path.join(hqRoot, ".hqsyncignore");
36
- if (fs.existsSync(ignorePath)) {
37
- const content = fs.readFileSync(ignorePath, "utf-8");
38
- ig.add(content);
39
- }
94
+ // Layer 2: repo's .gitignore (common case — covers most build dirs already)
95
+ const gitignore = readIgnoreFile(path.join(hqRoot, ".gitignore"));
96
+ if (gitignore) ig.add(gitignore);
97
+
98
+ // Layer 3: sync-specific overrides. .hqignore is the documented name;
99
+ // .hqsyncignore is the legacy name we still honor.
100
+ const hqignore =
101
+ readIgnoreFile(path.join(hqRoot, ".hqignore")) ??
102
+ readIgnoreFile(path.join(hqRoot, ".hqsyncignore"));
103
+ if (hqignore) ig.add(hqignore);
40
104
 
41
105
  return (filePath: string): boolean => {
42
106
  const relative = path.relative(hqRoot, filePath);
package/src/index.ts CHANGED
@@ -1,182 +1,111 @@
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
7
 
6
- import * as fs from "fs";
7
- import * as path from "path";
8
- import { authenticate, hasCredentials, readCredentials } from "./auth.js";
9
- import {
10
- startDaemon as _startDaemon,
11
- stopDaemon as _stopDaemon,
12
- isDaemonRunning,
13
- } from "./daemon.js";
14
- import { readJournal, writeJournal, hashFile, updateEntry } from "./journal.js";
15
- import { uploadFile, downloadFile, listRemoteFiles } from "./s3.js";
16
- import { createIgnoreFilter, isWithinSizeLimit } from "./ignore.js";
17
- import type { SyncStatus, PushResult, PullResult } from "./types.js";
8
+ export {
9
+ resolveEntityContext,
10
+ refreshEntityContext,
11
+ clearContextCache,
12
+ isExpiringSoon,
13
+ } from "./context.js";
14
+
15
+ export {
16
+ uploadFile,
17
+ downloadFile,
18
+ listRemoteFiles,
19
+ deleteRemoteFile,
20
+ headRemoteFile,
21
+ } from "./s3.js";
22
+
23
+ export type { RemoteFile } from "./s3.js";
24
+
25
+ export {
26
+ readJournal,
27
+ writeJournal,
28
+ hashFile,
29
+ updateEntry,
30
+ getEntry,
31
+ removeEntry,
32
+ getJournalPath,
33
+ } from "./journal.js";
18
34
 
19
- export type { SyncStatus, PushResult, PullResult } from "./types.js";
35
+ export {
36
+ createIgnoreFilter,
37
+ isWithinSizeLimit,
38
+ } from "./ignore.js";
20
39
 
21
- // Cognito identity helpers — used by `hq auth refresh` and any consumer
22
- // that needs a valid HQ access token (deploy skill, onboarding, etc.).
40
+ // Cognito browser-OAuth (VLT-9)
23
41
  export {
24
42
  browserLogin,
25
43
  refreshTokens,
26
- getValidAccessToken,
27
44
  loadCachedTokens,
28
45
  saveCachedTokens,
29
46
  clearCachedTokens,
30
47
  isExpiring,
48
+ getValidAccessToken,
31
49
  CognitoAuthError,
32
50
  } from "./cognito-auth.js";
33
51
  export type { CognitoAuthConfig, CognitoTokens } from "./cognito-auth.js";
34
52
 
35
- /**
36
- * Initialize cloud sync — authenticate and provision bucket
37
- */
38
- export async function initSync(hqRoot: string): Promise<void> {
39
- if (hasCredentials()) {
40
- console.log(" Already authenticated. Use 'hq sync start' to begin syncing.");
41
- return;
42
- }
43
-
44
- console.log(" Setting up IndigoAI cloud sync...");
45
- const creds = await authenticate();
46
- console.log(` ✓ Authenticated as ${creds.userId}`);
47
- console.log(` ✓ Bucket: ${creds.bucket}`);
48
- console.log(` ✓ Region: ${creds.region}`);
49
- console.log();
50
- console.log(" Run 'hq sync start' to begin syncing.");
51
- }
52
-
53
- /**
54
- * Start the background sync daemon
55
- */
56
- export async function startDaemon(hqRoot: string): Promise<void> {
57
- if (!hasCredentials()) {
58
- throw new Error("Not authenticated. Run 'hq sync init' first.");
59
- }
60
- _startDaemon(hqRoot);
61
- }
62
-
63
- /**
64
- * Stop the background sync daemon
65
- */
66
- export async function stopDaemon(hqRoot: string): Promise<void> {
67
- _stopDaemon(hqRoot);
68
- }
69
-
70
- /**
71
- * Get current sync status
72
- */
73
- export async function getStatus(hqRoot: string): Promise<SyncStatus> {
74
- const journal = readJournal(hqRoot);
75
- const creds = readCredentials();
76
- const running = isDaemonRunning(hqRoot);
77
- const errors: string[] = [];
78
-
79
- if (!creds) {
80
- errors.push("Not authenticated — run 'hq sync init'");
81
- }
82
-
83
- return {
84
- running,
85
- lastSync: journal.lastSync || null,
86
- fileCount: Object.keys(journal.files).length,
87
- bucket: creds?.bucket || null,
88
- errors,
89
- };
90
- }
91
-
92
- /**
93
- * Force push all local files to S3
94
- */
95
- export async function pushAll(hqRoot: string): Promise<PushResult> {
96
- const shouldSync = createIgnoreFilter(hqRoot);
97
- const journal = readJournal(hqRoot);
98
- let filesUploaded = 0;
99
- let bytesUploaded = 0;
100
-
101
- const files = walkDir(hqRoot, hqRoot, shouldSync);
102
-
103
- for (const { absolutePath, relativePath } of files) {
104
- if (!isWithinSizeLimit(absolutePath)) continue;
105
-
106
- try {
107
- const hash = hashFile(absolutePath);
108
- const stat = fs.statSync(absolutePath);
109
-
110
- await uploadFile(absolutePath, relativePath);
111
- updateEntry(journal, relativePath, hash, stat.size, "up");
112
- filesUploaded++;
113
- bytesUploaded += stat.size;
114
- } catch (err) {
115
- console.error(
116
- ` Failed: ${relativePath} — ${err instanceof Error ? err.message : err}`
117
- );
118
- }
119
- }
120
-
121
- writeJournal(hqRoot, journal);
122
- return { filesUploaded, bytesUploaded };
123
- }
124
-
125
- /**
126
- * Force pull all remote files to local
127
- */
128
- export async function pullAll(hqRoot: string): Promise<PullResult> {
129
- const journal = readJournal(hqRoot);
130
- let filesDownloaded = 0;
131
- let bytesDownloaded = 0;
132
-
133
- const remoteFiles = await listRemoteFiles();
134
-
135
- for (const file of remoteFiles) {
136
- try {
137
- const localPath = path.join(hqRoot, file.relativePath);
138
- await downloadFile(file.relativePath, localPath);
139
-
140
- const hash = hashFile(localPath);
141
- updateEntry(journal, file.relativePath, hash, file.size, "down");
142
- filesDownloaded++;
143
- bytesDownloaded += file.size;
144
- } catch (err) {
145
- console.error(
146
- ` Failed: ${file.relativePath} — ${err instanceof Error ? err.message : err}`
147
- );
148
- }
149
- }
150
-
151
- writeJournal(hqRoot, journal);
152
- return { filesDownloaded, bytesDownloaded };
153
- }
154
-
155
- // Helper: recursively walk a directory
156
- function walkDir(
157
- dir: string,
158
- root: string,
159
- filter: (p: string) => boolean
160
- ): { absolutePath: string; relativePath: string }[] {
161
- const results: { absolutePath: string; relativePath: string }[] = [];
162
-
163
- if (!fs.existsSync(dir)) return results;
164
-
165
- const entries = fs.readdirSync(dir, { withFileTypes: true });
166
- for (const entry of entries) {
167
- const absolutePath = path.join(dir, entry.name);
168
-
169
- if (!filter(absolutePath)) continue;
170
-
171
- if (entry.isDirectory()) {
172
- results.push(...walkDir(absolutePath, root, filter));
173
- } else if (entry.isFile()) {
174
- results.push({
175
- absolutePath,
176
- relativePath: path.relative(root, absolutePath),
177
- });
178
- }
179
- }
180
-
181
- return results;
182
- }
53
+ // VaultClient SDK (VLT-7)
54
+ export { VaultClient } from "./vault-client.js";
55
+ export {
56
+ VaultClientError,
57
+ VaultAuthError,
58
+ VaultPermissionDeniedError,
59
+ VaultNotFoundError,
60
+ VaultConflictError,
61
+ } from "./vault-client.js";
62
+ export type {
63
+ MembershipRole,
64
+ MembershipStatus,
65
+ Membership,
66
+ CreateInviteInput,
67
+ CreateInviteResult,
68
+ AcceptInviteResult,
69
+ UpdateRoleInput,
70
+ EntityInfo,
71
+ CreateEntityInput,
72
+ CreateEntityResult,
73
+ PendingInviteByEmail,
74
+ } from "./vault-client.js";
75
+
76
+ // STS child vending (VLT-8)
77
+ export type {
78
+ TaskAction,
79
+ TaskScope,
80
+ VendChildInput,
81
+ VendChildResult,
82
+ StsChildCredentials,
83
+ } from "./vault-client.js";
84
+
85
+ // CLI commands
86
+ export { share, sync } from "./cli/index.js";
87
+ export type { ShareOptions, ShareResult, SyncOptions, SyncResult, SyncProgressEvent } from "./cli/index.js";
88
+ export { resolveConflict, showDiff } from "./cli/index.js";
89
+ export type { ConflictStrategy, ConflictInfo, ConflictResolution } from "./cli/index.js";
90
+
91
+ // Membership CLI commands (VLT-7)
92
+ export { invite, listInvites, revokeInvite } from "./cli/index.js";
93
+ export type { InviteOptions, InviteResult, InviteListOptions, InviteRevokeOptions } from "./cli/index.js";
94
+ export { accept, parseToken } from "./cli/index.js";
95
+ export type { AcceptOptions, AcceptResult } from "./cli/index.js";
96
+ export { promote } from "./cli/index.js";
97
+ export type { PromoteOptions, PromoteResult } from "./cli/index.js";
98
+
99
+ export type {
100
+ EntityContext,
101
+ VaultCredentials,
102
+ VaultServiceConfig,
103
+ SyncConfig,
104
+ Credentials,
105
+ JournalEntry,
106
+ SyncJournal,
107
+ SyncStatus,
108
+ PushResult,
109
+ PullResult,
110
+ DaemonState,
111
+ } from "./types.js";