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.
- package/.dfx/local/network-id +4 -0
- package/.next/trace +2 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/AGENTS.md +43 -0
- package/CHANGELOG.md +196 -0
- package/LICENSE +21 -0
- package/PLAN_VAULT_INTEGRATION.md +318 -0
- package/README.md +253 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-28-967Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-54-29-032Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-373Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T17-57-42-428Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-132Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-52-25-247Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-216Z.json +28 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T18-54-09-283Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-772Z.backup +1 -0
- package/backups/agentvault-backup-test-agent-2026-02-12T22-18-22-793Z.json +28 -0
- package/backups/test-backup.json +28 -0
- package/dist/cli/commands/approve.d.ts +4 -0
- package/dist/cli/commands/approve.js +232 -0
- package/dist/cli/commands/archive.d.ts +4 -0
- package/dist/cli/commands/archive.js +192 -0
- package/dist/cli/commands/backup.d.ts +4 -0
- package/dist/cli/commands/backup.js +164 -0
- package/dist/cli/commands/cloud-backup.d.ts +4 -0
- package/dist/cli/commands/cloud-backup.js +221 -0
- package/dist/cli/commands/cycles.d.ts +8 -0
- package/dist/cli/commands/cycles.js +83 -0
- package/dist/cli/commands/decrypt.d.ts +16 -0
- package/dist/cli/commands/decrypt.js +101 -0
- package/dist/cli/commands/deploy.d.ts +32 -0
- package/dist/cli/commands/deploy.js +208 -0
- package/dist/cli/commands/exec.d.ts +26 -0
- package/dist/cli/commands/exec.js +109 -0
- package/dist/cli/commands/fetch.d.ts +23 -0
- package/dist/cli/commands/fetch.js +164 -0
- package/dist/cli/commands/health.d.ts +8 -0
- package/dist/cli/commands/health.js +72 -0
- package/dist/cli/commands/identity.d.ts +8 -0
- package/dist/cli/commands/identity.js +140 -0
- package/dist/cli/commands/inference.d.ts +4 -0
- package/dist/cli/commands/inference.js +225 -0
- package/dist/cli/commands/info.d.ts +8 -0
- package/dist/cli/commands/info.js +59 -0
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.js +135 -0
- package/dist/cli/commands/instrument.d.ts +8 -0
- package/dist/cli/commands/instrument.js +35 -0
- package/dist/cli/commands/list.d.ts +36 -0
- package/dist/cli/commands/list.js +173 -0
- package/dist/cli/commands/logs.d.ts +8 -0
- package/dist/cli/commands/logs.js +96 -0
- package/dist/cli/commands/monitor.d.ts +8 -0
- package/dist/cli/commands/monitor.js +84 -0
- package/dist/cli/commands/network.d.ts +14 -0
- package/dist/cli/commands/network.js +258 -0
- package/dist/cli/commands/package.d.ts +36 -0
- package/dist/cli/commands/package.js +188 -0
- package/dist/cli/commands/profile.d.ts +8 -0
- package/dist/cli/commands/profile.js +76 -0
- package/dist/cli/commands/promote.d.ts +8 -0
- package/dist/cli/commands/promote.js +89 -0
- package/dist/cli/commands/rebuild.d.ts +21 -0
- package/dist/cli/commands/rebuild.js +140 -0
- package/dist/cli/commands/rollback.d.ts +8 -0
- package/dist/cli/commands/rollback.js +120 -0
- package/dist/cli/commands/show.d.ts +36 -0
- package/dist/cli/commands/show.js +200 -0
- package/dist/cli/commands/stats.d.ts +8 -0
- package/dist/cli/commands/stats.js +34 -0
- package/dist/cli/commands/status.d.ts +14 -0
- package/dist/cli/commands/status.js +83 -0
- package/dist/cli/commands/test.d.ts +8 -0
- package/dist/cli/commands/test.js +109 -0
- package/dist/cli/commands/tokens.d.ts +8 -0
- package/dist/cli/commands/tokens.js +62 -0
- package/dist/cli/commands/trace.d.ts +8 -0
- package/dist/cli/commands/trace.js +68 -0
- package/dist/cli/commands/wallet-export.d.ts +13 -0
- package/dist/cli/commands/wallet-export.js +140 -0
- package/dist/cli/commands/wallet-history.d.ts +10 -0
- package/dist/cli/commands/wallet-history.js +127 -0
- package/dist/cli/commands/wallet-import.d.ts +10 -0
- package/dist/cli/commands/wallet-import.js +209 -0
- package/dist/cli/commands/wallet-multi-send.d.ts +17 -0
- package/dist/cli/commands/wallet-multi-send.js +195 -0
- package/dist/cli/commands/wallet-process-queue.d.ts +19 -0
- package/dist/cli/commands/wallet-process-queue.js +209 -0
- package/dist/cli/commands/wallet-sign.d.ts +13 -0
- package/dist/cli/commands/wallet-sign.js +207 -0
- package/dist/cli/commands/wallet.d.ts +12 -0
- package/dist/cli/commands/wallet.js +794 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +96 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.js +14 -0
- package/fixup_1_0_OSS_release.md +136 -0
- package/fixup_REALEASE_PRD.md +136 -0
- package/package.json +79 -0
- package/pnpm-workspace.yaml +5 -0
- package/scripts/dev-dashboard.mjs +84 -0
- package/site/README.md +63 -0
- package/site/docusaurus.config.ts +148 -0
- package/site/package-lock.json +18383 -0
- package/site/package.json +47 -0
- package/site/sidebars.ts +86 -0
- package/site/static/.gitkeep +0 -0
- package/site/static/img/logo.svg +28 -0
- package/site/static/img/og-image.svg +35 -0
- package/src/archival/archive-manager.ts +372 -0
- package/src/archival/arweave-client.ts +289 -0
- package/src/archival/index.ts +8 -0
- package/src/backup/backup.ts +315 -0
- package/src/backup/index.ts +7 -0
- package/src/cloud-storage/cloud-sync.ts +461 -0
- package/src/cloud-storage/index.ts +11 -0
- package/src/cloud-storage/provider-detector.ts +198 -0
- package/src/cloud-storage/types.ts +104 -0
- package/src/debugging/index.ts +6 -0
- package/src/debugging/logs.ts +193 -0
- package/src/debugging/types.ts +100 -0
- package/src/deployment/deployer.ts +274 -0
- package/src/deployment/icpClient.ts +620 -0
- package/src/deployment/index.ts +46 -0
- package/src/deployment/promotion.ts +161 -0
- package/src/deployment/types.ts +111 -0
- package/src/icp/batch.ts +374 -0
- package/src/icp/cycles.ts +50 -0
- package/src/icp/environment.ts +215 -0
- package/src/icp/icpcli.ts +438 -0
- package/src/icp/icwasm.ts +222 -0
- package/src/icp/identity.ts +77 -0
- package/src/icp/index.ts +94 -0
- package/src/icp/optimization.ts +242 -0
- package/src/icp/tokens.ts +36 -0
- package/src/icp/tool-detector.ts +110 -0
- package/src/icp/types.ts +574 -0
- package/src/index.ts +25 -0
- package/src/inference/bittensor-client.ts +304 -0
- package/src/inference/index.ts +8 -0
- package/src/inference/inference-manager.ts +327 -0
- package/src/metrics/index.ts +7 -0
- package/src/metrics/metrics.ts +186 -0
- package/src/monitoring/alerting.ts +190 -0
- package/src/monitoring/health.ts +197 -0
- package/src/monitoring/index.ts +38 -0
- package/src/monitoring/info.ts +114 -0
- package/src/monitoring/types.ts +99 -0
- package/src/network/index.ts +5 -0
- package/src/network/network-config.ts +129 -0
- package/src/packaging/compiler.ts +647 -0
- package/src/packaging/config-persistence.ts +135 -0
- package/src/packaging/config-schemas.ts +156 -0
- package/src/packaging/detector.ts +220 -0
- package/src/packaging/index.ts +90 -0
- package/src/packaging/packager.ts +118 -0
- package/src/packaging/parsers/clawdbot.ts +278 -0
- package/src/packaging/parsers/cline.ts +223 -0
- package/src/packaging/parsers/generic.ts +266 -0
- package/src/packaging/parsers/goose.ts +214 -0
- package/src/packaging/parsers/index.ts +11 -0
- package/src/packaging/serializer.ts +260 -0
- package/src/packaging/types.ts +144 -0
- package/src/packaging/wasmedge-compiler.ts +406 -0
- package/src/security/index.ts +17 -0
- package/src/security/multisig.ts +415 -0
- package/src/security/types.ts +416 -0
- package/src/security/vetkeys.ts +655 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/local-runner.ts +264 -0
- package/src/testing/types.ts +104 -0
- package/src/wallet/cbor-serializer.ts +323 -0
- package/src/wallet/chain-dispatcher.ts +313 -0
- package/src/wallet/cross-chain-aggregator.ts +346 -0
- package/src/wallet/index.ts +76 -0
- package/src/wallet/key-derivation.ts +425 -0
- package/src/wallet/providers/base-provider.ts +154 -0
- package/src/wallet/providers/cketh-provider.ts +434 -0
- package/src/wallet/providers/polkadot-provider.ts +503 -0
- package/src/wallet/providers/solana-provider.ts +490 -0
- package/src/wallet/transaction-queue.ts +284 -0
- package/src/wallet/types.ts +178 -0
- package/src/wallet/vetkeys-adapter.ts +431 -0
- package/src/wallet/wallet-manager.ts +597 -0
- package/src/wallet/wallet-storage.ts +380 -0
- 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
|
+
}
|