@tuskydp/cli 0.3.0 → 0.4.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 (113) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/src/commands/account.d.ts.map +1 -1
  3. package/dist/src/commands/account.js +0 -1
  4. package/dist/src/commands/account.js.map +1 -1
  5. package/dist/src/commands/auth.d.ts.map +1 -1
  6. package/dist/src/commands/auth.js +8 -5
  7. package/dist/src/commands/auth.js.map +1 -1
  8. package/dist/src/commands/download.d.ts.map +1 -1
  9. package/dist/src/commands/download.js +2 -59
  10. package/dist/src/commands/download.js.map +1 -1
  11. package/dist/src/commands/export.d.ts +5 -26
  12. package/dist/src/commands/export.d.ts.map +1 -1
  13. package/dist/src/commands/export.js +6 -46
  14. package/dist/src/commands/export.js.map +1 -1
  15. package/dist/src/commands/files.js +2 -2
  16. package/dist/src/commands/files.js.map +1 -1
  17. package/dist/src/commands/mcp.d.ts.map +1 -1
  18. package/dist/src/commands/mcp.js +15 -9
  19. package/dist/src/commands/mcp.js.map +1 -1
  20. package/dist/src/commands/sui.d.ts +3 -0
  21. package/dist/src/commands/sui.d.ts.map +1 -0
  22. package/dist/src/commands/sui.js +64 -0
  23. package/dist/src/commands/sui.js.map +1 -0
  24. package/dist/src/commands/trash.js +1 -1
  25. package/dist/src/commands/trash.js.map +1 -1
  26. package/dist/src/commands/upload.d.ts.map +1 -1
  27. package/dist/src/commands/upload.js +3 -49
  28. package/dist/src/commands/upload.js.map +1 -1
  29. package/dist/src/commands/vault.d.ts.map +1 -1
  30. package/dist/src/commands/vault.js +2 -24
  31. package/dist/src/commands/vault.js.map +1 -1
  32. package/dist/src/config.d.ts +2 -2
  33. package/dist/src/config.d.ts.map +1 -1
  34. package/dist/src/config.js +2 -3
  35. package/dist/src/config.js.map +1 -1
  36. package/dist/src/index.js +2 -4
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/lib/resolve.d.ts.map +1 -1
  39. package/dist/src/lib/resolve.js +4 -5
  40. package/dist/src/lib/resolve.js.map +1 -1
  41. package/dist/src/mcp/context.d.ts +1 -9
  42. package/dist/src/mcp/context.d.ts.map +1 -1
  43. package/dist/src/mcp/context.js +1 -2
  44. package/dist/src/mcp/context.js.map +1 -1
  45. package/dist/src/mcp/server.d.ts.map +1 -1
  46. package/dist/src/mcp/server.js +0 -58
  47. package/dist/src/mcp/server.js.map +1 -1
  48. package/dist/src/mcp/tools/account.d.ts.map +1 -1
  49. package/dist/src/mcp/tools/account.js +1 -3
  50. package/dist/src/mcp/tools/account.js.map +1 -1
  51. package/dist/src/mcp/tools/files.d.ts +2 -3
  52. package/dist/src/mcp/tools/files.d.ts.map +1 -1
  53. package/dist/src/mcp/tools/files.js +18 -56
  54. package/dist/src/mcp/tools/files.js.map +1 -1
  55. package/dist/src/mcp/tools/vaults.js +2 -2
  56. package/dist/src/mcp/tools/vaults.js.map +1 -1
  57. package/dist/src/tui/files-panel.d.ts +0 -1
  58. package/dist/src/tui/files-panel.d.ts.map +1 -1
  59. package/dist/src/tui/files-panel.js +1 -2
  60. package/dist/src/tui/files-panel.js.map +1 -1
  61. package/dist/src/tui/index.d.ts.map +1 -1
  62. package/dist/src/tui/index.js +7 -42
  63. package/dist/src/tui/index.js.map +1 -1
  64. package/dist/src/tui/overview.d.ts.map +1 -1
  65. package/dist/src/tui/overview.js +2 -6
  66. package/dist/src/tui/overview.js.map +1 -1
  67. package/dist/src/tui/trash-screen.js +1 -1
  68. package/dist/src/tui/trash-screen.js.map +1 -1
  69. package/package.json +3 -3
  70. package/src/__tests__/seal.test.ts +7 -54
  71. package/src/commands/account.ts +0 -1
  72. package/src/commands/auth.ts +7 -5
  73. package/src/commands/download.ts +2 -63
  74. package/src/commands/export.ts +7 -67
  75. package/src/commands/files.ts +2 -2
  76. package/src/commands/mcp.ts +16 -10
  77. package/src/commands/sui.ts +69 -0
  78. package/src/commands/trash.ts +1 -1
  79. package/src/commands/upload.ts +3 -59
  80. package/src/commands/vault.ts +2 -23
  81. package/src/config.ts +3 -4
  82. package/src/index.ts +2 -4
  83. package/src/lib/resolve.ts +3 -4
  84. package/src/mcp/context.ts +1 -11
  85. package/src/mcp/server.ts +0 -69
  86. package/src/mcp/tools/account.ts +1 -3
  87. package/src/mcp/tools/files.ts +19 -70
  88. package/src/mcp/tools/vaults.ts +3 -3
  89. package/src/tui/files-panel.ts +1 -3
  90. package/src/tui/index.ts +7 -51
  91. package/src/tui/overview.ts +2 -5
  92. package/src/tui/trash-screen.ts +1 -1
  93. package/dist/src/commands/decrypt.d.ts +0 -15
  94. package/dist/src/commands/decrypt.d.ts.map +0 -1
  95. package/dist/src/commands/decrypt.js +0 -256
  96. package/dist/src/commands/decrypt.js.map +0 -1
  97. package/dist/src/commands/encryption.d.ts +0 -3
  98. package/dist/src/commands/encryption.d.ts.map +0 -1
  99. package/dist/src/commands/encryption.js +0 -254
  100. package/dist/src/commands/encryption.js.map +0 -1
  101. package/dist/src/crypto.d.ts +0 -32
  102. package/dist/src/crypto.d.ts.map +0 -1
  103. package/dist/src/crypto.js +0 -121
  104. package/dist/src/crypto.js.map +0 -1
  105. package/dist/src/lib/keyring.d.ts +0 -4
  106. package/dist/src/lib/keyring.d.ts.map +0 -1
  107. package/dist/src/lib/keyring.js +0 -49
  108. package/dist/src/lib/keyring.js.map +0 -1
  109. package/src/__tests__/crypto.test.ts +0 -315
  110. package/src/commands/decrypt.ts +0 -309
  111. package/src/commands/encryption.ts +0 -305
  112. package/src/crypto.ts +0 -165
  113. package/src/lib/keyring.ts +0 -50
@@ -1,14 +1,13 @@
1
1
  /**
2
2
  * `tusky export` — Export file metadata for disaster recovery.
3
3
  *
4
- * Dumps all file records (including Walrus blob IDs, encryption keys,
5
- * and epoch info) to a JSON file. This allows users to recover their
6
- * data directly from Walrus if the Tusky service is unavailable.
4
+ * Dumps all file records (including Walrus blob IDs and epoch info)
5
+ * to a JSON file. This allows users to recover their data directly
6
+ * from Walrus if the Tusky service is unavailable.
7
7
  *
8
8
  * The export contains everything needed to:
9
9
  * 1. Fetch files from Walrus aggregators using blobId
10
- * 2. Decrypt private vault files using wrappedKey + iv + master key
11
- * 3. Renew Walrus epochs using blobObjectId + a Sui wallet
10
+ * 2. Renew Walrus epochs using blobObjectId + a Sui wallet
12
11
  */
13
12
 
14
13
  import type { Command } from 'commander';
@@ -35,20 +34,10 @@ export interface ExportedFile {
35
34
  walrusBlobObjectId: string | null;
36
35
  /** Epoch when Walrus storage expires (renew before this) */
37
36
  walrusEpochEnd: string | null;
38
- /** Whether the file is client-side encrypted */
37
+ /** Whether the file is client-side encrypted (SEAL for shared vaults) */
39
38
  encrypted: boolean;
40
- /** AES-256-GCM file key, wrapped with the master key (base64) */
41
- wrappedKey: string | null;
42
- /** AES-256-GCM initialization vector (base64) */
43
- encryptionIv: string | null;
44
- /** SHA-256 of the plaintext — for integrity verification after decryption */
45
- plaintextChecksumSha256: string | null;
46
- /** Original plaintext size in bytes (before encryption) */
47
- plaintextSizeBytes: number | null;
48
39
  /** Whether the filename is encrypted */
49
40
  nameEncrypted: boolean;
50
- /** AES-256-GCM encrypted filename+mimeType blob (private vaults) */
51
- encryptedName: string | null;
52
41
  /** SEAL-encrypted filename+mimeType blob (shared vaults) */
53
42
  encryptedMetadata: string | null;
54
43
  createdAt: string;
@@ -62,16 +51,6 @@ export interface ExportManifest {
62
51
  email: string;
63
52
  plan: string;
64
53
  };
65
- /** Account encryption params — needed to derive master key from passphrase */
66
- encryption: {
67
- setupComplete: boolean;
68
- /** PBKDF2 salt (base64) */
69
- salt: string | null;
70
- /** HMAC verifier for passphrase validation (base64) */
71
- verifier: string | null;
72
- /** Master key wrapped with passphrase-derived key (base64) */
73
- encryptedMasterKey: string | null;
74
- };
75
54
  walrusNetwork: string;
76
55
  walrusAggregatorUrl: string;
77
56
  totalFiles: number;
@@ -85,7 +64,7 @@ export interface ExportManifest {
85
64
  export function registerExportCommand(program: Command) {
86
65
  program
87
66
  .command('export')
88
- .description('Export file metadata for disaster recovery (Walrus blob IDs, encryption keys)')
67
+ .description('Export file metadata for disaster recovery (Walrus blob IDs, epoch info)')
89
68
  .option('-o, --output <path>', 'Output file path', 'tusky-export.json')
90
69
  .option('--stdout', 'Write JSON manifest to stdout instead of a file')
91
70
  .option('--vault <vaultId>', 'Export only files from a specific vault')
@@ -98,9 +77,8 @@ export function registerExportCommand(program: Command) {
98
77
  spinner.start();
99
78
 
100
79
  try {
101
- // Fetch account + encryption params
80
+ // Fetch account
102
81
  const account = await sdk.account.get();
103
- const encryptionParams = await sdk.account.getEncryptionParams();
104
82
 
105
83
  // Fetch all vaults
106
84
  spinner.text = 'Fetching vaults...';
@@ -146,12 +124,7 @@ export function registerExportCommand(program: Command) {
146
124
  walrusBlobObjectId: file.walrusBlobObjectId,
147
125
  walrusEpochEnd: file.walrusEpochEnd,
148
126
  encrypted: file.encrypted,
149
- wrappedKey: file.wrappedKey,
150
- encryptionIv: file.encryptionIv,
151
- plaintextChecksumSha256: file.plaintextChecksumSha256,
152
- plaintextSizeBytes: file.plaintextSizeBytes,
153
127
  nameEncrypted: file.nameEncrypted ?? false,
154
- encryptedName: file.encryptedName ?? null,
155
128
  encryptedMetadata: file.encryptedMetadata ?? null,
156
129
  createdAt: file.createdAt,
157
130
  });
@@ -178,12 +151,6 @@ export function registerExportCommand(program: Command) {
178
151
  email: account.email,
179
152
  plan: account.planName,
180
153
  },
181
- encryption: {
182
- setupComplete: encryptionParams.setupComplete,
183
- salt: encryptionParams.salt ?? null,
184
- verifier: encryptionParams.verifier ?? null,
185
- encryptedMasterKey: encryptionParams.encryptedMasterKey ?? null,
186
- },
187
154
  walrusNetwork: isMainnet ? 'mainnet' : 'testnet',
188
155
  walrusAggregatorUrl,
189
156
  totalFiles: exportedFiles.length,
@@ -201,30 +168,6 @@ export function registerExportCommand(program: Command) {
201
168
  ' OR: walrus read <walrusBlobId> -o <filename>',
202
169
  ' OR: tusky rehydrate <walrusBlobId> -o <filename>',
203
170
  '',
204
- '--- DECRYPT PRIVATE VAULT FILES ---',
205
- 'Files with encrypted=true are AES-256-GCM encrypted.',
206
- '',
207
- 'Option A — Using the Tusky CLI decrypt command (easiest):',
208
- ' tusky decrypt <encrypted-file> --export <this-file.json> -o <output>',
209
- ' This reads the wrappedKey/iv from this manifest and prompts for your passphrase.',
210
- ' You can also pass --passphrase <passphrase> or set TUSKYDP_PASSWORD env var.',
211
- ' If the Tusky API is still reachable, you can skip the export and use:',
212
- ' tusky decrypt <encrypted-file> --file-id <fileId> -o <output>',
213
- '',
214
- 'Option B — Using the standalone script (zero dependencies, fully offline):',
215
- ' node tusky-decrypt.mjs <encrypted-file> <output> <passphrase> <salt> <wrappedKey> <iv> [encryptedMasterKey]',
216
- ' The salt and encryptedMasterKey come from your account encryption params.',
217
- '',
218
- 'Option C — Manual decryption (any language with AES-256-GCM + PBKDF2):',
219
- ' 1. Derive wrapping key: PBKDF2(passphrase, salt, 600000 iterations, SHA-256, 32 bytes)',
220
- ' 2. If your account has an encryptedMasterKey, unwrap it: AES-256-GCM-decrypt(encryptedMasterKey, wrappingKey)',
221
- ' Otherwise the wrapping key IS the master key (legacy accounts)',
222
- ' 3. Unwrap the file key: AES-256-GCM-decrypt(wrappedKey, masterKey)',
223
- ' wrappedKey format: [12-byte IV | ciphertext | 16-byte auth tag]',
224
- ' 4. Decrypt the file: AES-256-GCM-decrypt(fileData, fileKey, encryptionIv)',
225
- ' File data format: [ciphertext | 16-byte auth tag]',
226
- ' 5. Verify integrity: SHA-256(plaintext) should match plaintextChecksumSha256',
227
- '',
228
171
  '--- RENEW WALRUS EPOCHS ---',
229
172
  'Files with a walrusBlobObjectId can have their storage extended:',
230
173
  ' walrus extend <walrusBlobObjectId> --epochs 5',
@@ -248,16 +191,13 @@ export function registerExportCommand(program: Command) {
248
191
  if (!options.stdout) {
249
192
  // Summary stats (only for disk output — stdout is clean JSON)
250
193
  const withBlob = exportedFiles.filter((f) => f.walrusBlobId).length;
251
- const encrypted = exportedFiles.filter((f) => f.encrypted).length;
252
194
  const hotOnly = exportedFiles.filter((f) => !f.walrusBlobId && f.status === 'hot').length;
253
195
 
254
196
  console.log(` On Walrus: ${withBlob} files (recoverable from Walrus network)`);
255
- console.log(` Encrypted: ${encrypted} files (require passphrase to decrypt)`);
256
197
  if (hotOnly > 0) {
257
198
  console.log(chalk.yellow(` Hot only: ${hotOnly} files (NOT yet on Walrus — only in Tusky hot cache)`));
258
199
  }
259
200
  console.log('');
260
- console.log(chalk.yellow(' Store this file securely — it contains encryption keys.'));
261
201
  console.log(chalk.dim(' See the "recovery.instructions" field inside for full recovery steps.'));
262
202
  }
263
203
  } catch (err: any) {
@@ -62,7 +62,7 @@ export function registerFileCommands(program: Command) {
62
62
 
63
63
  const row = [
64
64
  f.name,
65
- formatBytes(f.plaintextSizeBytes || f.sizeBytes),
65
+ formatBytes(f.sizeBytes),
66
66
  statusColor(f.status),
67
67
  ];
68
68
  if (showFolder) {
@@ -113,7 +113,7 @@ export function registerFileCommands(program: Command) {
113
113
  }
114
114
 
115
115
  console.log(`Name: ${file.name}`);
116
- console.log(`Size: ${formatBytes(file.plaintextSizeBytes || file.sizeBytes)}`);
116
+ console.log(`Size: ${formatBytes(file.sizeBytes)}`);
117
117
  console.log(`MIME: ${file.mimeType}`);
118
118
  console.log(`Status: ${file.status}`);
119
119
  console.log(`Encrypted: ${file.encrypted ? 'Yes' : 'No'}`);
@@ -9,7 +9,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
9
9
  import { join, dirname, resolve } from 'path';
10
10
  import { homedir } from 'os';
11
11
  import chalk from 'chalk';
12
- import { getApiUrl, getApiKey, cliConfig } from '../config.js';
12
+ import { getApiUrl, getApiKey, cliConfig, getSuiPrivateKey } from '../config.js';
13
13
  import { startMcpServer } from '../mcp/server.js';
14
14
 
15
15
  // ---------------------------------------------------------------------------
@@ -93,15 +93,16 @@ function getTuskyBinaryCommand(): { command: string; args: string[] } {
93
93
  return { command: 'npx', args: ['-y', '@tuskydp/cli', 'mcp', 'serve'] };
94
94
  }
95
95
 
96
- function buildMcpServerEntry(apiKey?: string, apiUrl?: string) {
96
+ function buildMcpServerEntry(apiKey?: string, apiUrl?: string, suiPrivKey?: string) {
97
97
  const { command, args } = getTuskyBinaryCommand();
98
98
 
99
99
  const env: Record<string, string> = {};
100
100
  if (apiKey) env.TUSKYDP_API_KEY = apiKey;
101
101
  if (apiUrl) env.TUSKYDP_API_URL = apiUrl;
102
102
 
103
- // Placeholder for password user fills in
104
- env.TUSKYDP_PASSWORD = '';
103
+ // Include Sui private key if provided or stored in config
104
+ const resolvedSuiKey = suiPrivKey || getSuiPrivateKey() || '';
105
+ env.TUSKYDP_SUI_PRIVATE_KEY = resolvedSuiKey;
105
106
 
106
107
  const entry: Record<string, unknown> = {
107
108
  command,
@@ -158,7 +159,8 @@ export function registerMcpCommands(program: Command) {
158
159
  .option('--target <target>', 'Target client: claude-code, claude-desktop, cursor', 'claude-code')
159
160
  .option('--api-key <key>', 'API key to embed (defaults to stored key)')
160
161
  .option('--api-url <url>', 'API URL override')
161
- .action(async (options: { target: string; apiKey?: string; apiUrl?: string }) => {
162
+ .option('--sui-priv-key <key>', 'Sui Ed25519 private key for shared vault access (defaults to stored key)')
163
+ .action(async (options: { target: string; apiKey?: string; apiUrl?: string; suiPrivKey?: string }) => {
162
164
  const targets = getTargets();
163
165
  const target = targets.find((t) => t.name === options.target);
164
166
 
@@ -177,8 +179,8 @@ export function registerMcpCommands(program: Command) {
177
179
  // Only include non-default values
178
180
  const resolvedApiUrl = apiUrl && apiUrl !== 'https://api.tusky.ai' ? apiUrl : undefined;
179
181
 
180
- // Build the server entry
181
- const serverEntry = buildMcpServerEntry(apiKey, resolvedApiUrl);
182
+ // Build the server entry (SUI key: flag > config)
183
+ const serverEntry = buildMcpServerEntry(apiKey, resolvedApiUrl, options.suiPrivKey);
182
184
 
183
185
  // Read existing config
184
186
  const config = readJsonFile(target.configPath);
@@ -205,9 +207,13 @@ export function registerMcpCommands(program: Command) {
205
207
  console.log('');
206
208
  }
207
209
 
208
- console.log(chalk.yellow(' Remember to set TUSKYDP_PASSWORD in the config for private vault encryption:'));
209
- console.log(chalk.dim(` Edit the env block in ${target.configPath}`));
210
- console.log('');
210
+ const suiKey = options.suiPrivKey || getSuiPrivateKey();
211
+ if (!suiKey) {
212
+ console.log(chalk.yellow(' Note: No Sui key found. Set it up for shared vault access:'));
213
+ console.log(chalk.dim(' Run: tusky sui setup <suiprivkey1...>'));
214
+ console.log(chalk.dim(' Then re-run: tusky mcp install-config'));
215
+ console.log('');
216
+ }
211
217
 
212
218
  if (options.target === 'claude-desktop') {
213
219
  console.log(chalk.dim(' Restart Claude Desktop to pick up the changes.'));
@@ -0,0 +1,69 @@
1
+ import type { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { cliConfig } from '../config.js';
4
+ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
5
+
6
+ export function registerSuiCommands(program: Command) {
7
+ const sui = program
8
+ .command('sui')
9
+ .description('Manage Sui wallet key for shared vault access');
10
+
11
+ // ── tusky sui setup ──────────────────────────────────────────────────
12
+ sui
13
+ .command('setup <private-key>')
14
+ .description('Save a Sui Ed25519 private key to config for shared vault operations')
15
+ .action((privateKey: string) => {
16
+ let address: string;
17
+ try {
18
+ const keypair = Ed25519Keypair.fromSecretKey(privateKey);
19
+ address = keypair.toSuiAddress();
20
+ } catch {
21
+ console.error(chalk.red('Invalid Sui private key. Expected suiprivkey1... format.'));
22
+ process.exit(1);
23
+ }
24
+
25
+ cliConfig.set('suiPrivateKey', privateKey);
26
+ console.log(chalk.green('Sui private key saved to config.'));
27
+ console.log(chalk.dim(` Sui address: ${address}`));
28
+ console.log('');
29
+ console.log(chalk.yellow(' Security note: The key is stored in your local config file.'));
30
+ console.log(chalk.dim(` Location: ${cliConfig.path}`));
31
+ console.log(chalk.dim(' Run `tusky sui clear` to remove it.'));
32
+ });
33
+
34
+ // ── tusky sui show ───────────────────────────────────────────────────
35
+ sui
36
+ .command('show')
37
+ .description('Show the configured Sui address (does not reveal the private key)')
38
+ .action(() => {
39
+ const envKey = process.env.TUSKYDP_SUI_PRIVATE_KEY;
40
+ const configKey = cliConfig.get('suiPrivateKey');
41
+ const key = envKey || configKey;
42
+ const source = envKey ? 'env var (TUSKYDP_SUI_PRIVATE_KEY)' : 'config';
43
+
44
+ if (!key) {
45
+ console.log(chalk.dim('No Sui private key configured.'));
46
+ console.log(chalk.dim('Run: tusky sui setup <private-key>'));
47
+ return;
48
+ }
49
+
50
+ try {
51
+ const keypair = Ed25519Keypair.fromSecretKey(key);
52
+ const address = keypair.toSuiAddress();
53
+ console.log(`Sui address: ${address}`);
54
+ console.log(chalk.dim(`Source: ${source}`));
55
+ } catch {
56
+ console.error(chalk.red('Stored Sui key is invalid. Run: tusky sui setup <private-key>'));
57
+ process.exit(1);
58
+ }
59
+ });
60
+
61
+ // ── tusky sui clear ──────────────────────────────────────────────────
62
+ sui
63
+ .command('clear')
64
+ .description('Remove the stored Sui private key from config')
65
+ .action(() => {
66
+ cliConfig.delete('suiPrivateKey');
67
+ console.log(chalk.green('Sui private key removed from config.'));
68
+ });
69
+ }
@@ -51,7 +51,7 @@ export function registerTrashCommands(program: Command) {
51
51
  for (const f of trashed.files) {
52
52
  table.push([
53
53
  f.name,
54
- formatBytes(f.plaintextSizeBytes || f.sizeBytes),
54
+ formatBytes(f.sizeBytes),
55
55
  f.status,
56
56
  f.deletedAt ? formatDate(f.deletedAt) : 'N/A',
57
57
  chalk.dim(shortId(f.id)),
@@ -9,8 +9,6 @@ import { createSDKClient } from '../sdk.js';
9
9
  import { resolveVault } from '../lib/resolve.js';
10
10
  import { formatBytes } from '../lib/output.js';
11
11
  import { createSpinner } from '../lib/progress.js';
12
- import { encryptBuffer, encryptMetadata } from '../crypto.js';
13
- import { loadMasterKey } from '../lib/keyring.js';
14
12
  import { isSealConfigured, sealEncrypt, sealEncryptMetadata, getSuiKeypair } from '../seal.js';
15
13
 
16
14
  async function readStdin(): Promise<Buffer> {
@@ -79,18 +77,8 @@ export async function uploadCommand(paths: string[], options: {
79
77
 
80
78
  const vaultId = await resolveVault(sdk, options.vault);
81
79
  const vault = await sdk.vaults.get(vaultId);
82
- const isPrivate = vault.visibility === 'private';
83
80
  const isShared = vault.visibility === 'shared' && isSealConfigured(vault);
84
81
 
85
- let masterKey: Buffer | null = null;
86
- if (isPrivate) {
87
- masterKey = loadMasterKey();
88
- if (!masterKey) {
89
- console.error(chalk.red('Encryption session not unlocked. Run: tusky encryption unlock'));
90
- process.exit(1);
91
- }
92
- }
93
-
94
82
  const spinner = createSpinner(`Uploading ${fileName}`);
95
83
  spinner.start();
96
84
 
@@ -98,28 +86,12 @@ export async function uploadCommand(paths: string[], options: {
98
86
  const mimeType = lookup(fileName) || 'application/octet-stream';
99
87
  let uploadBody: Buffer;
100
88
  let encryptionMeta: {
101
- wrappedKey?: string;
102
- encryptionIv?: string;
103
- plaintextSizeBytes?: number;
104
- plaintextChecksumSha256?: string;
105
89
  sealIdentity?: string;
106
90
  sealEncryptedObject?: string;
107
- encryptedName?: string;
108
91
  encryptedMetadata?: string;
109
92
  } = {};
110
93
 
111
- if (isPrivate && masterKey) {
112
- spinner.text = `Encrypting ${fileName}...`;
113
- const { ciphertext, wrappedKey, iv, plaintextChecksum } = encryptBuffer(fileBuffer, masterKey);
114
- uploadBody = ciphertext;
115
- encryptionMeta = {
116
- wrappedKey,
117
- encryptionIv: iv,
118
- plaintextSizeBytes: fileBuffer.length,
119
- plaintextChecksumSha256: plaintextChecksum,
120
- encryptedName: encryptMetadata(fileName, mimeType, masterKey),
121
- };
122
- } else if (isShared) {
94
+ if (isShared) {
123
95
  spinner.text = `SEAL encrypting ${fileName}...`;
124
96
  const fileNonce = randomUUID();
125
97
  const sealResult = await sealEncrypt(new Uint8Array(fileBuffer), vault, fileNonce);
@@ -127,7 +99,6 @@ export async function uploadCommand(paths: string[], options: {
127
99
  encryptionMeta = {
128
100
  sealIdentity: sealResult.sealIdentity,
129
101
  sealEncryptedObject: sealResult.sealEncryptedObject,
130
- plaintextSizeBytes: fileBuffer.length,
131
102
  encryptedMetadata: await sealEncryptMetadata(fileName, mimeType, vault, fileNonce),
132
103
  };
133
104
  } else {
@@ -169,18 +140,8 @@ export async function uploadCommand(paths: string[], options: {
169
140
  // ── File path mode (original behavior) ───────────────────────────
170
141
  const vaultId = await resolveVault(sdk, options.vault);
171
142
  const vault = await sdk.vaults.get(vaultId);
172
- const isPrivate = vault.visibility === 'private';
173
143
  const isShared = vault.visibility === 'shared' && isSealConfigured(vault);
174
144
 
175
- let masterKey: Buffer | null = null;
176
- if (isPrivate) {
177
- masterKey = loadMasterKey();
178
- if (!masterKey) {
179
- console.error(chalk.red('Encryption session not unlocked. Run: tusky encryption unlock'));
180
- process.exit(1);
181
- }
182
- }
183
-
184
145
  if (isShared) {
185
146
  const keypair = getSuiKeypair();
186
147
  if (!keypair) {
@@ -209,28 +170,12 @@ export async function uploadCommand(paths: string[], options: {
209
170
 
210
171
  let uploadBody: Buffer;
211
172
  let encryptionMeta: {
212
- wrappedKey?: string;
213
- encryptionIv?: string;
214
- plaintextSizeBytes?: number;
215
- plaintextChecksumSha256?: string;
216
173
  sealIdentity?: string;
217
174
  sealEncryptedObject?: string;
218
- encryptedName?: string;
219
175
  encryptedMetadata?: string;
220
176
  } = {};
221
177
 
222
- if (isPrivate && masterKey) {
223
- spinner.text = `Encrypting ${basename(filePath)}...`;
224
- const { ciphertext, wrappedKey, iv, plaintextChecksum } = encryptBuffer(fileBuffer, masterKey);
225
- uploadBody = ciphertext;
226
- encryptionMeta = {
227
- wrappedKey,
228
- encryptionIv: iv,
229
- plaintextSizeBytes: stat.size,
230
- plaintextChecksumSha256: plaintextChecksum,
231
- encryptedName: encryptMetadata(basename(filePath), mimeType, masterKey),
232
- };
233
- } else if (isShared) {
178
+ if (isShared) {
234
179
  spinner.text = `SEAL encrypting ${basename(filePath)}...`;
235
180
  const fileNonce = randomUUID();
236
181
  const sealResult = await sealEncrypt(new Uint8Array(fileBuffer), vault, fileNonce);
@@ -238,7 +183,6 @@ export async function uploadCommand(paths: string[], options: {
238
183
  encryptionMeta = {
239
184
  sealIdentity: sealResult.sealIdentity,
240
185
  sealEncryptedObject: sealResult.sealEncryptedObject,
241
- plaintextSizeBytes: stat.size,
242
186
  encryptedMetadata: await sealEncryptMetadata(basename(filePath), mimeType, vault, fileNonce),
243
187
  };
244
188
  } else {
@@ -283,7 +227,7 @@ export async function uploadCommand(paths: string[], options: {
283
227
  }
284
228
 
285
229
  if (filePaths.length > 1) {
286
- const label = isPrivate ? 'private' : isShared ? 'shared' : 'public';
230
+ const label = isShared ? 'shared' : 'public';
287
231
  console.log(`\nUploaded ${successCount} file(s) (${formatBytes(totalSize)}) to vault "${vault.name}" [${label}]`);
288
232
  }
289
233
  }
@@ -8,7 +8,6 @@ import { resolveVault } from '../lib/resolve.js';
8
8
 
9
9
  /** Map vault visibility to a display label. */
10
10
  function visibilityLabel(v: string): string {
11
- if (v === 'private') return 'private';
12
11
  if (v === 'shared') return 'shared';
13
12
  return 'public';
14
13
  }
@@ -18,34 +17,14 @@ export function registerVaultCommands(program: Command) {
18
17
 
19
18
  // ── create ──────────────────────────────────────────────────────────
20
19
  vault.command('create <name>')
21
- .description('Create a new vault (private/encrypted by default)')
22
- .option('--public', 'Create as public vault (unencrypted, shareable via URL)')
20
+ .description('Create a new vault (public by default)')
23
21
  .option('--shared', 'Create as shared vault (SEAL-encrypted, requires linked Sui address)')
24
22
  .option('--description <text>', 'Vault description')
25
23
  .action(async (name: string, options) => {
26
24
  const sdk = getSDKClientFromParent(vault);
27
- let visibility: 'public' | 'private' | 'shared' = 'private';
28
- if (options.public) visibility = 'public';
25
+ let visibility: 'public' | 'shared' = 'public';
29
26
  if (options.shared) visibility = 'shared';
30
27
 
31
- if (options.public && options.shared) {
32
- console.error(chalk.red('Cannot use both --public and --shared.'));
33
- return;
34
- }
35
-
36
- if (visibility === 'private') {
37
- try {
38
- const { setupComplete } = await sdk.account.getEncryptionParams();
39
- if (!setupComplete) {
40
- console.log(chalk.red('Encryption must be set up before creating private vaults.'));
41
- console.log(chalk.dim(' Run: tusky encryption setup'));
42
- return;
43
- }
44
- } catch {
45
- // If endpoint fails, proceed anyway
46
- }
47
- }
48
-
49
28
  if (visibility === 'shared') {
50
29
  try {
51
30
  const acct = await sdk.account.get();
package/src/config.ts CHANGED
@@ -5,7 +5,7 @@ export interface TuskyDPConfig {
5
5
  apiKey?: string;
6
6
  defaultVault?: string;
7
7
  outputFormat: 'table' | 'json' | 'plain';
8
- encryptionEnabled: boolean;
8
+ suiPrivateKey?: string;
9
9
  }
10
10
 
11
11
  export const cliConfig = new Conf<TuskyDPConfig>({
@@ -13,7 +13,6 @@ export const cliConfig = new Conf<TuskyDPConfig>({
13
13
  defaults: {
14
14
  apiUrl: 'https://api.tusky.ai',
15
15
  outputFormat: 'table',
16
- encryptionEnabled: true,
17
16
  },
18
17
  });
19
18
 
@@ -39,8 +38,8 @@ export function getOutputFormat(override?: string): 'table' | 'json' | 'plain' {
39
38
 
40
39
  /**
41
40
  * Get the Sui private key for SEAL operations on shared vaults.
42
- * Only read from env var (never persisted to config for security).
41
+ * Priority: TUSKYDP_SUI_PRIVATE_KEY env var > stored config key.
43
42
  */
44
43
  export function getSuiPrivateKey(): string | null {
45
- return process.env.TUSKYDP_SUI_PRIVATE_KEY || null;
44
+ return process.env.TUSKYDP_SUI_PRIVATE_KEY || cliConfig.get('suiPrivateKey') || null;
46
45
  }
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { registerAuthCommands } from './commands/auth.js';
4
- import { registerEncryptionCommands } from './commands/encryption.js';
5
4
  import { registerVaultCommands } from './commands/vault.js';
6
5
  import { registerFileCommands } from './commands/files.js';
7
6
  import { registerAccountCommands } from './commands/account.js';
@@ -14,8 +13,8 @@ import { downloadCommand } from './commands/download.js';
14
13
  import { rehydrateCommand } from './commands/rehydrate.js';
15
14
  import { registerTuiCommand } from './commands/tui.js';
16
15
  import { registerMcpCommands } from './commands/mcp.js';
16
+ import { registerSuiCommands } from './commands/sui.js';
17
17
  import { registerExportCommand } from './commands/export.js';
18
- import { registerDecryptCommand } from './commands/decrypt.js';
19
18
  import { CLI_VERSION } from './version.js';
20
19
 
21
20
  const program = new Command();
@@ -39,7 +38,6 @@ program
39
38
  });
40
39
 
41
40
  registerAuthCommands(program);
42
- registerEncryptionCommands(program);
43
41
  registerVaultCommands(program);
44
42
  registerFileCommands(program);
45
43
  registerFolderCommands(program);
@@ -49,8 +47,8 @@ registerWalletCommands(program);
49
47
  registerAccountCommands(program);
50
48
  registerTuiCommand(program);
51
49
  registerMcpCommands(program);
50
+ registerSuiCommands(program);
52
51
  registerExportCommand(program);
53
- registerDecryptCommand(program);
54
52
 
55
53
  // Direct shortcuts for common operations
56
54
  program.command('upload [paths...]')
@@ -7,12 +7,11 @@ export async function resolveVault(sdk: TuskyClient, vaultRef?: string): Promise
7
7
  const defaultVault = cliConfig.get('defaultVault');
8
8
  if (defaultVault) return defaultVault;
9
9
 
10
- // Fall back to the user's default vault
10
+ // Fall back to the first vault
11
11
  const vaults = await sdk.vaults.list();
12
- const defaultV = vaults.find((v) => v.isDefault);
13
- if (defaultV) return defaultV.id;
12
+ if (vaults.length > 0) return vaults[0].id;
14
13
 
15
- throw new Error('No default vault found. Create one with: tusky vault create <name>');
14
+ throw new Error('No vault found. Create one with: tusky vault create <name>');
16
15
  }
17
16
 
18
17
  // If it looks like a UUID, use it directly
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Shared context for MCP tool handlers.
3
3
  *
4
- * Holds the authenticated SDK client, the unlocked master key for
5
- * private vault encryption, and the Sui keypair for shared vault
4
+ * Holds the authenticated SDK client and the Sui keypair for shared vault
6
5
  * SEAL encryption/decryption.
7
6
  */
8
7
 
@@ -13,15 +12,6 @@ export interface McpContext {
13
12
  /** Authenticated Tusky SDK client. */
14
13
  sdk: TuskyClient;
15
14
 
16
- /**
17
- * Returns the in-memory master key, or null if encryption was not
18
- * unlocked (i.e. TUSKYDP_PASSWORD was not provided or setup is incomplete).
19
- */
20
- getMasterKey(): Buffer | null;
21
-
22
- /** True when the master key is available for encrypt/decrypt operations. */
23
- isEncryptionReady(): boolean;
24
-
25
15
  /**
26
16
  * Returns the Sui Ed25519 keypair for SEAL operations, or null if
27
17
  * TUSKYDP_SUI_PRIVATE_KEY was not provided.