agentvault 1.0.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 (188) hide show
  1. package/.dfx/local/network-id +4 -0
  2. package/.next/trace +2 -0
  3. package/.vercel/README.txt +11 -0
  4. package/.vercel/project.json +1 -0
  5. package/AGENTS.md +43 -0
  6. package/CHANGELOG.md +196 -0
  7. package/LICENSE +21 -0
  8. package/PLAN_VAULT_INTEGRATION.md +318 -0
  9. package/README.md +253 -0
  10. package/backups/agentvault-backup-test-agent-2026-02-12T17-54-28-967Z.json +28 -0
  11. package/backups/agentvault-backup-test-agent-2026-02-12T17-54-29-032Z.backup +1 -0
  12. package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-373Z.json +28 -0
  13. package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-428Z.backup +1 -0
  14. package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-132Z.json +28 -0
  15. package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-247Z.backup +1 -0
  16. package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-216Z.json +28 -0
  17. package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-283Z.backup +1 -0
  18. package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-772Z.backup +1 -0
  19. package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-793Z.json +28 -0
  20. package/backups/test-backup.json +28 -0
  21. package/dist/cli/commands/approve.d.ts +4 -0
  22. package/dist/cli/commands/approve.js +232 -0
  23. package/dist/cli/commands/archive.d.ts +4 -0
  24. package/dist/cli/commands/archive.js +192 -0
  25. package/dist/cli/commands/backup.d.ts +4 -0
  26. package/dist/cli/commands/backup.js +164 -0
  27. package/dist/cli/commands/cloud-backup.d.ts +4 -0
  28. package/dist/cli/commands/cloud-backup.js +221 -0
  29. package/dist/cli/commands/cycles.d.ts +8 -0
  30. package/dist/cli/commands/cycles.js +83 -0
  31. package/dist/cli/commands/decrypt.d.ts +16 -0
  32. package/dist/cli/commands/decrypt.js +101 -0
  33. package/dist/cli/commands/deploy.d.ts +32 -0
  34. package/dist/cli/commands/deploy.js +208 -0
  35. package/dist/cli/commands/exec.d.ts +26 -0
  36. package/dist/cli/commands/exec.js +109 -0
  37. package/dist/cli/commands/fetch.d.ts +23 -0
  38. package/dist/cli/commands/fetch.js +164 -0
  39. package/dist/cli/commands/health.d.ts +8 -0
  40. package/dist/cli/commands/health.js +72 -0
  41. package/dist/cli/commands/identity.d.ts +8 -0
  42. package/dist/cli/commands/identity.js +140 -0
  43. package/dist/cli/commands/inference.d.ts +4 -0
  44. package/dist/cli/commands/inference.js +225 -0
  45. package/dist/cli/commands/info.d.ts +8 -0
  46. package/dist/cli/commands/info.js +59 -0
  47. package/dist/cli/commands/init.d.ts +19 -0
  48. package/dist/cli/commands/init.js +135 -0
  49. package/dist/cli/commands/instrument.d.ts +8 -0
  50. package/dist/cli/commands/instrument.js +35 -0
  51. package/dist/cli/commands/list.d.ts +36 -0
  52. package/dist/cli/commands/list.js +173 -0
  53. package/dist/cli/commands/logs.d.ts +8 -0
  54. package/dist/cli/commands/logs.js +96 -0
  55. package/dist/cli/commands/monitor.d.ts +8 -0
  56. package/dist/cli/commands/monitor.js +84 -0
  57. package/dist/cli/commands/network.d.ts +14 -0
  58. package/dist/cli/commands/network.js +258 -0
  59. package/dist/cli/commands/package.d.ts +36 -0
  60. package/dist/cli/commands/package.js +188 -0
  61. package/dist/cli/commands/profile.d.ts +8 -0
  62. package/dist/cli/commands/profile.js +76 -0
  63. package/dist/cli/commands/promote.d.ts +8 -0
  64. package/dist/cli/commands/promote.js +89 -0
  65. package/dist/cli/commands/rebuild.d.ts +21 -0
  66. package/dist/cli/commands/rebuild.js +140 -0
  67. package/dist/cli/commands/rollback.d.ts +8 -0
  68. package/dist/cli/commands/rollback.js +120 -0
  69. package/dist/cli/commands/show.d.ts +36 -0
  70. package/dist/cli/commands/show.js +200 -0
  71. package/dist/cli/commands/stats.d.ts +8 -0
  72. package/dist/cli/commands/stats.js +34 -0
  73. package/dist/cli/commands/status.d.ts +14 -0
  74. package/dist/cli/commands/status.js +83 -0
  75. package/dist/cli/commands/test.d.ts +8 -0
  76. package/dist/cli/commands/test.js +109 -0
  77. package/dist/cli/commands/tokens.d.ts +8 -0
  78. package/dist/cli/commands/tokens.js +62 -0
  79. package/dist/cli/commands/trace.d.ts +8 -0
  80. package/dist/cli/commands/trace.js +68 -0
  81. package/dist/cli/commands/wallet-export.d.ts +13 -0
  82. package/dist/cli/commands/wallet-export.js +140 -0
  83. package/dist/cli/commands/wallet-history.d.ts +10 -0
  84. package/dist/cli/commands/wallet-history.js +127 -0
  85. package/dist/cli/commands/wallet-import.d.ts +10 -0
  86. package/dist/cli/commands/wallet-import.js +209 -0
  87. package/dist/cli/commands/wallet-multi-send.d.ts +17 -0
  88. package/dist/cli/commands/wallet-multi-send.js +195 -0
  89. package/dist/cli/commands/wallet-process-queue.d.ts +19 -0
  90. package/dist/cli/commands/wallet-process-queue.js +209 -0
  91. package/dist/cli/commands/wallet-sign.d.ts +13 -0
  92. package/dist/cli/commands/wallet-sign.js +207 -0
  93. package/dist/cli/commands/wallet.d.ts +12 -0
  94. package/dist/cli/commands/wallet.js +794 -0
  95. package/dist/cli/index.d.ts +10 -0
  96. package/dist/cli/index.js +96 -0
  97. package/dist/vitest.config.d.ts +3 -0
  98. package/dist/vitest.config.js +14 -0
  99. package/fixup_1_0_OSS_release.md +136 -0
  100. package/fixup_REALEASE_PRD.md +136 -0
  101. package/package.json +79 -0
  102. package/pnpm-workspace.yaml +5 -0
  103. package/scripts/dev-dashboard.mjs +84 -0
  104. package/site/README.md +63 -0
  105. package/site/docusaurus.config.ts +148 -0
  106. package/site/package-lock.json +18383 -0
  107. package/site/package.json +47 -0
  108. package/site/sidebars.ts +86 -0
  109. package/site/static/.gitkeep +0 -0
  110. package/site/static/img/logo.svg +28 -0
  111. package/site/static/img/og-image.svg +35 -0
  112. package/src/archival/archive-manager.ts +372 -0
  113. package/src/archival/arweave-client.ts +289 -0
  114. package/src/archival/index.ts +8 -0
  115. package/src/backup/backup.ts +315 -0
  116. package/src/backup/index.ts +7 -0
  117. package/src/cloud-storage/cloud-sync.ts +461 -0
  118. package/src/cloud-storage/index.ts +11 -0
  119. package/src/cloud-storage/provider-detector.ts +198 -0
  120. package/src/cloud-storage/types.ts +104 -0
  121. package/src/debugging/index.ts +6 -0
  122. package/src/debugging/logs.ts +193 -0
  123. package/src/debugging/types.ts +100 -0
  124. package/src/deployment/deployer.ts +274 -0
  125. package/src/deployment/icpClient.ts +620 -0
  126. package/src/deployment/index.ts +46 -0
  127. package/src/deployment/promotion.ts +161 -0
  128. package/src/deployment/types.ts +111 -0
  129. package/src/icp/batch.ts +374 -0
  130. package/src/icp/cycles.ts +50 -0
  131. package/src/icp/environment.ts +215 -0
  132. package/src/icp/icpcli.ts +438 -0
  133. package/src/icp/icwasm.ts +222 -0
  134. package/src/icp/identity.ts +77 -0
  135. package/src/icp/index.ts +94 -0
  136. package/src/icp/optimization.ts +242 -0
  137. package/src/icp/tokens.ts +36 -0
  138. package/src/icp/tool-detector.ts +110 -0
  139. package/src/icp/types.ts +574 -0
  140. package/src/index.ts +25 -0
  141. package/src/inference/bittensor-client.ts +304 -0
  142. package/src/inference/index.ts +8 -0
  143. package/src/inference/inference-manager.ts +327 -0
  144. package/src/metrics/index.ts +7 -0
  145. package/src/metrics/metrics.ts +186 -0
  146. package/src/monitoring/alerting.ts +190 -0
  147. package/src/monitoring/health.ts +197 -0
  148. package/src/monitoring/index.ts +38 -0
  149. package/src/monitoring/info.ts +114 -0
  150. package/src/monitoring/types.ts +99 -0
  151. package/src/network/index.ts +5 -0
  152. package/src/network/network-config.ts +129 -0
  153. package/src/packaging/compiler.ts +647 -0
  154. package/src/packaging/config-persistence.ts +135 -0
  155. package/src/packaging/config-schemas.ts +156 -0
  156. package/src/packaging/detector.ts +220 -0
  157. package/src/packaging/index.ts +90 -0
  158. package/src/packaging/packager.ts +118 -0
  159. package/src/packaging/parsers/clawdbot.ts +278 -0
  160. package/src/packaging/parsers/cline.ts +223 -0
  161. package/src/packaging/parsers/generic.ts +266 -0
  162. package/src/packaging/parsers/goose.ts +214 -0
  163. package/src/packaging/parsers/index.ts +11 -0
  164. package/src/packaging/serializer.ts +260 -0
  165. package/src/packaging/types.ts +144 -0
  166. package/src/packaging/wasmedge-compiler.ts +406 -0
  167. package/src/security/index.ts +17 -0
  168. package/src/security/multisig.ts +415 -0
  169. package/src/security/types.ts +416 -0
  170. package/src/security/vetkeys.ts +655 -0
  171. package/src/testing/index.ts +6 -0
  172. package/src/testing/local-runner.ts +264 -0
  173. package/src/testing/types.ts +104 -0
  174. package/src/wallet/cbor-serializer.ts +323 -0
  175. package/src/wallet/chain-dispatcher.ts +313 -0
  176. package/src/wallet/cross-chain-aggregator.ts +346 -0
  177. package/src/wallet/index.ts +76 -0
  178. package/src/wallet/key-derivation.ts +425 -0
  179. package/src/wallet/providers/base-provider.ts +154 -0
  180. package/src/wallet/providers/cketh-provider.ts +434 -0
  181. package/src/wallet/providers/polkadot-provider.ts +503 -0
  182. package/src/wallet/providers/solana-provider.ts +490 -0
  183. package/src/wallet/transaction-queue.ts +284 -0
  184. package/src/wallet/types.ts +178 -0
  185. package/src/wallet/vetkeys-adapter.ts +431 -0
  186. package/src/wallet/wallet-manager.ts +597 -0
  187. package/src/wallet/wallet-storage.ts +380 -0
  188. package/vercel.json +8 -0
@@ -0,0 +1,461 @@
1
+ /**
2
+ * Cloud Sync
3
+ *
4
+ * Archive and restore AgentVault data to/from a cloud-synced
5
+ * local directory. Creates a self-contained folder with a JSON
6
+ * manifest so any cloud provider can sync it as plain files.
7
+ *
8
+ * No blockchain, no crypto, no API keys — just files.
9
+ */
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import os from 'node:os';
14
+ import crypto from 'node:crypto';
15
+ import { VERSION } from '../index.js';
16
+ import { resolveCloudBackupDir } from './provider-detector.js';
17
+ import type {
18
+ CloudArchiveManifest,
19
+ CloudArchiveFileEntry,
20
+ CloudArchiveOptions,
21
+ CloudArchiveResult,
22
+ CloudRestoreResult,
23
+ DiscoveredArchive,
24
+ } from './types.js';
25
+
26
+ const MANIFEST_FILENAME = 'manifest.json';
27
+ const MANIFEST_VERSION = '1.0';
28
+
29
+ /**
30
+ * Return the default AgentVault data directory.
31
+ */
32
+ export function getDefaultVaultDir(): string {
33
+ return path.join(os.homedir(), '.agentvault');
34
+ }
35
+
36
+ /**
37
+ * Compute SHA-256 hex digest of a buffer.
38
+ */
39
+ function sha256(data: Buffer): string {
40
+ return crypto.createHash('sha256').update(data).digest('hex');
41
+ }
42
+
43
+ /**
44
+ * Recursively collect all files under a directory.
45
+ * Returns paths relative to `baseDir`.
46
+ */
47
+ function collectFiles(baseDir: string): string[] {
48
+ const results: string[] = [];
49
+
50
+ function walk(dir: string): void {
51
+ if (!fs.existsSync(dir)) return;
52
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
53
+ for (const entry of entries) {
54
+ const full = path.join(dir, entry.name);
55
+ if (entry.isDirectory()) {
56
+ walk(full);
57
+ } else if (entry.isFile()) {
58
+ results.push(path.relative(baseDir, full));
59
+ }
60
+ }
61
+ }
62
+
63
+ walk(baseDir);
64
+ return results;
65
+ }
66
+
67
+ /**
68
+ * Get the list of source directories/files to include,
69
+ * scoped to a specific agent or the entire vault.
70
+ */
71
+ function getSourcePaths(
72
+ options: CloudArchiveOptions,
73
+ vaultDir: string,
74
+ ): { label: string; absPath: string }[] {
75
+ const sources: { label: string; absPath: string }[] = [];
76
+ const agentName = options.agentName;
77
+
78
+ if (options.includeConfigs) {
79
+ if (agentName) {
80
+ sources.push({
81
+ label: 'configs',
82
+ absPath: path.join(vaultDir, 'agents', agentName),
83
+ });
84
+ } else {
85
+ sources.push({
86
+ label: 'configs',
87
+ absPath: path.join(vaultDir, 'agents'),
88
+ });
89
+ }
90
+ }
91
+
92
+ if (options.includeWallets) {
93
+ if (agentName) {
94
+ sources.push({
95
+ label: 'wallets',
96
+ absPath: path.join(vaultDir, 'wallets', agentName),
97
+ });
98
+ } else {
99
+ sources.push({
100
+ label: 'wallets',
101
+ absPath: path.join(vaultDir, 'wallets'),
102
+ });
103
+ }
104
+ }
105
+
106
+ if (options.includeBackups) {
107
+ sources.push({
108
+ label: 'backups',
109
+ absPath: path.join(vaultDir, 'backups'),
110
+ });
111
+ }
112
+
113
+ if (options.includeNetworks) {
114
+ sources.push({
115
+ label: 'networks',
116
+ absPath: path.join(vaultDir, 'networks'),
117
+ });
118
+ }
119
+
120
+ return sources;
121
+ }
122
+
123
+ /**
124
+ * Archive AgentVault data to a cloud-synced directory.
125
+ *
126
+ * Creates a timestamped folder inside the cloud backup dir
127
+ * containing a copy of the selected vault data plus a manifest.
128
+ *
129
+ * @param providerBasePath - Root path of the cloud provider sync directory
130
+ * @param options - What to include in the archive
131
+ * @param subdirectory - Subdirectory name inside provider (default: AgentVault-Backups)
132
+ * @param vaultDir - Path to the AgentVault data directory (default: ~/.agentvault)
133
+ */
134
+ export function archiveToCloud(
135
+ providerBasePath: string,
136
+ options: CloudArchiveOptions,
137
+ subdirectory?: string,
138
+ vaultDir?: string,
139
+ ): CloudArchiveResult {
140
+ try {
141
+ const effectiveVaultDir = vaultDir || getDefaultVaultDir();
142
+ const cloudDir = resolveCloudBackupDir(providerBasePath, subdirectory);
143
+ const timestamp = new Date()
144
+ .toISOString()
145
+ .replace(/[:.]/g, '-');
146
+ const folderName = options.agentName
147
+ ? `${options.agentName}-${timestamp}`
148
+ : `vault-${timestamp}`;
149
+ const archiveDir = path.join(cloudDir, folderName);
150
+
151
+ // Create archive directory
152
+ fs.mkdirSync(archiveDir, { recursive: true });
153
+
154
+ const sources = getSourcePaths(options, effectiveVaultDir);
155
+ const fileEntries: CloudArchiveFileEntry[] = [];
156
+ const components: string[] = [];
157
+ let totalBytes = 0;
158
+
159
+ for (const source of sources) {
160
+ if (!fs.existsSync(source.absPath)) continue;
161
+
162
+ const stat = fs.statSync(source.absPath);
163
+ const componentDir = path.join(archiveDir, source.label);
164
+
165
+ if (stat.isDirectory()) {
166
+ const files = collectFiles(source.absPath);
167
+ if (files.length === 0) continue;
168
+
169
+ components.push(source.label);
170
+
171
+ for (const relFile of files) {
172
+ const srcFile = path.join(source.absPath, relFile);
173
+ const destFile = path.join(componentDir, relFile);
174
+
175
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
176
+ fs.copyFileSync(srcFile, destFile);
177
+
178
+ const fileData = fs.readFileSync(destFile);
179
+ fileEntries.push({
180
+ relativePath: path.join(source.label, relFile),
181
+ sizeBytes: fileData.length,
182
+ checksum: sha256(fileData),
183
+ });
184
+ totalBytes += fileData.length;
185
+ }
186
+ } else {
187
+ // Single file (e.g. a specific agent config)
188
+ components.push(source.label);
189
+ fs.mkdirSync(componentDir, { recursive: true });
190
+ const destFile = path.join(componentDir, path.basename(source.absPath));
191
+ fs.copyFileSync(source.absPath, destFile);
192
+
193
+ const fileData = fs.readFileSync(destFile);
194
+ fileEntries.push({
195
+ relativePath: path.join(source.label, path.basename(source.absPath)),
196
+ sizeBytes: fileData.length,
197
+ checksum: sha256(fileData),
198
+ });
199
+ totalBytes += fileData.length;
200
+ }
201
+ }
202
+
203
+ if (fileEntries.length === 0) {
204
+ // Clean up empty dir
205
+ fs.rmSync(archiveDir, { recursive: true, force: true });
206
+ return {
207
+ success: false,
208
+ error: 'No data found to archive. Check that ~/.agentvault contains data.',
209
+ };
210
+ }
211
+
212
+ // Compute overall checksum from sorted file checksums
213
+ const overallChecksum = sha256(
214
+ Buffer.from(
215
+ fileEntries
216
+ .map((f) => f.checksum)
217
+ .sort()
218
+ .join(''),
219
+ ),
220
+ );
221
+
222
+ const manifest: CloudArchiveManifest = {
223
+ version: MANIFEST_VERSION,
224
+ createdAt: new Date().toISOString(),
225
+ platform: os.platform(),
226
+ hostname: os.hostname(),
227
+ agentVaultVersion: VERSION,
228
+ agentName: options.agentName,
229
+ components,
230
+ files: fileEntries,
231
+ checksum: overallChecksum,
232
+ };
233
+
234
+ const manifestPath = path.join(archiveDir, MANIFEST_FILENAME);
235
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
236
+
237
+ return {
238
+ success: true,
239
+ archivePath: archiveDir,
240
+ manifestPath,
241
+ fileCount: fileEntries.length,
242
+ totalBytes,
243
+ };
244
+ } catch (error) {
245
+ return {
246
+ success: false,
247
+ error: error instanceof Error ? error.message : 'Unknown error',
248
+ };
249
+ }
250
+ }
251
+
252
+ /**
253
+ * List available archives in a cloud backup directory.
254
+ */
255
+ export function listCloudArchives(
256
+ providerBasePath: string,
257
+ subdirectory?: string,
258
+ ): DiscoveredArchive[] {
259
+ const cloudDir = resolveCloudBackupDir(providerBasePath, subdirectory);
260
+
261
+ if (!fs.existsSync(cloudDir)) {
262
+ return [];
263
+ }
264
+
265
+ const entries = fs.readdirSync(cloudDir, { withFileTypes: true });
266
+ const archives: DiscoveredArchive[] = [];
267
+
268
+ for (const entry of entries) {
269
+ if (!entry.isDirectory()) continue;
270
+
271
+ const manifestPath = path.join(cloudDir, entry.name, MANIFEST_FILENAME);
272
+ if (!fs.existsSync(manifestPath)) continue;
273
+
274
+ try {
275
+ const raw = fs.readFileSync(manifestPath, 'utf8');
276
+ const manifest = JSON.parse(raw) as CloudArchiveManifest;
277
+
278
+ archives.push({
279
+ manifestPath,
280
+ archivePath: path.join(cloudDir, entry.name),
281
+ manifest,
282
+ });
283
+ } catch {
284
+ // Skip malformed manifests
285
+ }
286
+ }
287
+
288
+ // Sort newest first
289
+ archives.sort(
290
+ (a, b) =>
291
+ new Date(b.manifest.createdAt).getTime() -
292
+ new Date(a.manifest.createdAt).getTime(),
293
+ );
294
+
295
+ return archives;
296
+ }
297
+
298
+ /**
299
+ * Restore AgentVault data from a cloud archive.
300
+ *
301
+ * Copies files from the archive back into the vault directory,
302
+ * verifying checksums along the way.
303
+ *
304
+ * @param archivePath - Path to the archive directory
305
+ * @param overwrite - Whether to overwrite existing files
306
+ * @param vaultDir - Path to the AgentVault data directory (default: ~/.agentvault)
307
+ */
308
+ export function restoreFromCloud(
309
+ archivePath: string,
310
+ overwrite: boolean = false,
311
+ vaultDir?: string,
312
+ ): CloudRestoreResult {
313
+ try {
314
+ const effectiveVaultDir = vaultDir || getDefaultVaultDir();
315
+ const manifestPath = path.join(archivePath, MANIFEST_FILENAME);
316
+ if (!fs.existsSync(manifestPath)) {
317
+ return {
318
+ success: false,
319
+ warnings: [],
320
+ error: `No manifest found at ${manifestPath}`,
321
+ };
322
+ }
323
+
324
+ const raw = fs.readFileSync(manifestPath, 'utf8');
325
+ const manifest = JSON.parse(raw) as CloudArchiveManifest;
326
+ const warnings: string[] = [];
327
+ let restoredFiles = 0;
328
+
329
+ // Component-to-vault-dir mapping
330
+ const componentDirMap: Record<string, string> = {
331
+ configs: 'agents',
332
+ wallets: 'wallets',
333
+ backups: 'backups',
334
+ networks: 'networks',
335
+ };
336
+
337
+ for (const fileEntry of manifest.files) {
338
+ const srcFile = path.join(archivePath, fileEntry.relativePath);
339
+
340
+ if (!fs.existsSync(srcFile)) {
341
+ warnings.push(`Missing file in archive: ${fileEntry.relativePath}`);
342
+ continue;
343
+ }
344
+
345
+ // Verify checksum
346
+ const fileData = fs.readFileSync(srcFile);
347
+ const actualChecksum = sha256(fileData);
348
+ if (actualChecksum !== fileEntry.checksum) {
349
+ warnings.push(
350
+ `Checksum mismatch for ${fileEntry.relativePath} (expected ${fileEntry.checksum.slice(0, 8)}..., got ${actualChecksum.slice(0, 8)}...)`,
351
+ );
352
+ continue;
353
+ }
354
+
355
+ // Determine destination: map component prefix to vault dir
356
+ const firstSlash = fileEntry.relativePath.indexOf(path.sep);
357
+ // Handle both / and path.sep for cross-platform paths in manifests
358
+ const firstSlashAlt = fileEntry.relativePath.indexOf('/');
359
+ const slashIdx =
360
+ firstSlash >= 0 && firstSlashAlt >= 0
361
+ ? Math.min(firstSlash, firstSlashAlt)
362
+ : Math.max(firstSlash, firstSlashAlt);
363
+
364
+ if (slashIdx < 0) {
365
+ warnings.push(`Skipping file with no component prefix: ${fileEntry.relativePath}`);
366
+ continue;
367
+ }
368
+
369
+ const component = fileEntry.relativePath.slice(0, slashIdx);
370
+ const restOfPath = fileEntry.relativePath.slice(slashIdx + 1);
371
+ const vaultSubdir = componentDirMap[component];
372
+
373
+ if (!vaultSubdir) {
374
+ warnings.push(`Unknown component "${component}" for ${fileEntry.relativePath}`);
375
+ continue;
376
+ }
377
+
378
+ const destFile = path.join(effectiveVaultDir, vaultSubdir, restOfPath);
379
+
380
+ if (fs.existsSync(destFile) && !overwrite) {
381
+ warnings.push(`Skipping existing file (use --overwrite): ${destFile}`);
382
+ continue;
383
+ }
384
+
385
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
386
+ fs.copyFileSync(srcFile, destFile);
387
+ restoredFiles++;
388
+ }
389
+
390
+ return {
391
+ success: true,
392
+ restoredFiles,
393
+ components: manifest.components,
394
+ warnings,
395
+ };
396
+ } catch (error) {
397
+ return {
398
+ success: false,
399
+ warnings: [],
400
+ error: error instanceof Error ? error.message : 'Unknown error',
401
+ };
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Verify integrity of a cloud archive by checking all file checksums.
407
+ */
408
+ export function verifyCloudArchive(
409
+ archivePath: string,
410
+ ): { valid: boolean; errors: string[] } {
411
+ const errors: string[] = [];
412
+ const manifestPath = path.join(archivePath, MANIFEST_FILENAME);
413
+
414
+ if (!fs.existsSync(manifestPath)) {
415
+ return { valid: false, errors: ['Manifest file not found'] };
416
+ }
417
+
418
+ try {
419
+ const raw = fs.readFileSync(manifestPath, 'utf8');
420
+ const manifest = JSON.parse(raw) as CloudArchiveManifest;
421
+
422
+ for (const fileEntry of manifest.files) {
423
+ const filePath = path.join(archivePath, fileEntry.relativePath);
424
+
425
+ if (!fs.existsSync(filePath)) {
426
+ errors.push(`Missing: ${fileEntry.relativePath}`);
427
+ continue;
428
+ }
429
+
430
+ const fileData = fs.readFileSync(filePath);
431
+ const actualChecksum = sha256(fileData);
432
+
433
+ if (actualChecksum !== fileEntry.checksum) {
434
+ errors.push(
435
+ `Checksum mismatch: ${fileEntry.relativePath}`,
436
+ );
437
+ }
438
+ }
439
+
440
+ // Verify overall checksum
441
+ const expectedOverall = sha256(
442
+ Buffer.from(
443
+ manifest.files
444
+ .map((f) => f.checksum)
445
+ .sort()
446
+ .join(''),
447
+ ),
448
+ );
449
+
450
+ if (expectedOverall !== manifest.checksum) {
451
+ errors.push('Overall archive checksum mismatch');
452
+ }
453
+
454
+ return { valid: errors.length === 0, errors };
455
+ } catch (error) {
456
+ return {
457
+ valid: false,
458
+ errors: [error instanceof Error ? error.message : 'Unknown error'],
459
+ };
460
+ }
461
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Cloud Storage Module
3
+ *
4
+ * Archive and restore AgentVault data to consumer cloud storage
5
+ * providers (Google Drive, iCloud, Dropbox, OneDrive, etc.)
6
+ * using their local sync directories. No crypto required.
7
+ */
8
+
9
+ export * from './types.js';
10
+ export * from './provider-detector.js';
11
+ export * from './cloud-sync.js';
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Cloud Provider Detector
3
+ *
4
+ * Auto-detects consumer cloud storage providers by checking for
5
+ * their well-known local sync directories on the current platform.
6
+ * No API keys, no SDK installs — just plain filesystem checks.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import os from 'node:os';
12
+ import type { CloudProvider, DetectedProvider } from './types.js';
13
+
14
+ interface ProviderCandidate {
15
+ provider: CloudProvider;
16
+ label: string;
17
+ paths: string[];
18
+ }
19
+
20
+ /**
21
+ * Build the list of well-known sync directory paths per provider.
22
+ * Paths vary by OS, so we enumerate all known locations.
23
+ */
24
+ function getCandidates(): ProviderCandidate[] {
25
+ const home = os.homedir();
26
+ const platform = os.platform();
27
+
28
+ const candidates: ProviderCandidate[] = [
29
+ {
30
+ provider: 'google-drive',
31
+ label: 'Google Drive',
32
+ paths: [
33
+ path.join(home, 'Google Drive'),
34
+ path.join(home, 'My Drive'),
35
+ ...(platform === 'darwin'
36
+ ? [
37
+ path.join(home, 'Library', 'CloudStorage', 'GoogleDrive'),
38
+ path.join(
39
+ home,
40
+ 'Library',
41
+ 'CloudStorage',
42
+ 'GoogleDrive-My Drive',
43
+ ),
44
+ ]
45
+ : []),
46
+ ...(platform === 'win32'
47
+ ? [path.join('G:', 'My Drive')]
48
+ : []),
49
+ ],
50
+ },
51
+ {
52
+ provider: 'icloud-drive',
53
+ label: 'iCloud Drive',
54
+ paths:
55
+ platform === 'darwin'
56
+ ? [
57
+ path.join(
58
+ home,
59
+ 'Library',
60
+ 'Mobile Documents',
61
+ 'com~apple~CloudDocs',
62
+ ),
63
+ ]
64
+ : platform === 'win32'
65
+ ? [
66
+ path.join(home, 'iCloudDrive'),
67
+ path.join(
68
+ process.env['USERPROFILE'] || home,
69
+ 'iCloudDrive',
70
+ ),
71
+ ]
72
+ : [],
73
+ },
74
+ {
75
+ provider: 'dropbox',
76
+ label: 'Dropbox',
77
+ paths: [
78
+ path.join(home, 'Dropbox'),
79
+ ...(platform === 'win32'
80
+ ? [path.join('D:', 'Dropbox')]
81
+ : []),
82
+ ],
83
+ },
84
+ {
85
+ provider: 'onedrive',
86
+ label: 'OneDrive',
87
+ paths: [
88
+ path.join(home, 'OneDrive'),
89
+ ...(platform === 'win32'
90
+ ? [
91
+ path.join(
92
+ process.env['USERPROFILE'] || home,
93
+ 'OneDrive',
94
+ ),
95
+ ]
96
+ : []),
97
+ ...(platform === 'darwin'
98
+ ? [
99
+ path.join(home, 'Library', 'CloudStorage', 'OneDrive'),
100
+ ]
101
+ : []),
102
+ ],
103
+ },
104
+ ];
105
+
106
+ return candidates;
107
+ }
108
+
109
+ /**
110
+ * Check if a directory exists and is accessible.
111
+ */
112
+ function directoryExists(dirPath: string): boolean {
113
+ try {
114
+ const stat = fs.statSync(dirPath);
115
+ return stat.isDirectory();
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Detect all available cloud storage providers on this machine.
123
+ * Returns one entry per provider, using the first matching path found.
124
+ */
125
+ export function detectProviders(): DetectedProvider[] {
126
+ const candidates = getCandidates();
127
+ const results: DetectedProvider[] = [];
128
+
129
+ for (const candidate of candidates) {
130
+ let foundPath: string | null = null;
131
+
132
+ for (const candidatePath of candidate.paths) {
133
+ if (directoryExists(candidatePath)) {
134
+ foundPath = candidatePath;
135
+ break;
136
+ }
137
+ }
138
+
139
+ results.push({
140
+ provider: candidate.provider,
141
+ label: candidate.label,
142
+ path: foundPath || candidate.paths[0] || '',
143
+ available: foundPath !== null,
144
+ });
145
+ }
146
+
147
+ return results;
148
+ }
149
+
150
+ /**
151
+ * Detect only available (installed/syncing) providers.
152
+ */
153
+ export function detectAvailableProviders(): DetectedProvider[] {
154
+ return detectProviders().filter((p) => p.available);
155
+ }
156
+
157
+ /**
158
+ * Get the default AgentVault subdirectory name inside a cloud provider.
159
+ */
160
+ export function getCloudSubdirectory(): string {
161
+ return 'AgentVault-Backups';
162
+ }
163
+
164
+ /**
165
+ * Resolve the full cloud backup directory for a given provider path.
166
+ */
167
+ export function resolveCloudBackupDir(
168
+ providerPath: string,
169
+ subdirectory?: string,
170
+ ): string {
171
+ return path.join(providerPath, subdirectory || getCloudSubdirectory());
172
+ }
173
+
174
+ /**
175
+ * Build a DetectedProvider for a custom/user-specified path.
176
+ */
177
+ export function createCustomProvider(customPath: string): DetectedProvider {
178
+ return {
179
+ provider: 'custom',
180
+ label: 'Custom Directory',
181
+ path: customPath,
182
+ available: directoryExists(customPath),
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Get a human-readable label for a provider.
188
+ */
189
+ export function getProviderLabel(provider: CloudProvider): string {
190
+ const labels: Record<CloudProvider, string> = {
191
+ 'google-drive': 'Google Drive',
192
+ 'icloud-drive': 'iCloud Drive',
193
+ 'dropbox': 'Dropbox',
194
+ 'onedrive': 'OneDrive',
195
+ 'custom': 'Custom Directory',
196
+ };
197
+ return labels[provider];
198
+ }