@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
package/src/mcp/server.ts CHANGED
@@ -9,12 +9,6 @@
9
9
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
11
  import { TuskyClient, TuskyError } from '@tuskydp/sdk';
12
- import {
13
- deriveMasterKey,
14
- verifyPassphrase,
15
- unwrapMasterKey,
16
- } from '../crypto.js';
17
- import { loadMasterKey } from '../lib/keyring.js';
18
12
  import { getSuiKeypair } from '../seal.js';
19
13
  import type { McpContext } from './context.js';
20
14
 
@@ -27,58 +21,6 @@ import { registerTrashTools } from './tools/trash.js';
27
21
  import { registerSharedVaultTools } from './tools/sharedVaults.js';
28
22
  import { CLI_VERSION } from '../version.js';
29
23
 
30
- // ---------------------------------------------------------------------------
31
- // Encryption bootstrap
32
- // ---------------------------------------------------------------------------
33
-
34
- /**
35
- * Attempt to unlock the master key using (in order):
36
- * 1. TUSKYDP_PASSWORD env var → derive wrapping key → unwrap master key
37
- * 2. Existing session file (~/.tusky/session.enc)
38
- *
39
- * Returns the master key Buffer, or null if neither method works.
40
- */
41
- async function unlockMasterKey(sdk: TuskyClient): Promise<Buffer | null> {
42
- const password = process.env.TUSKYDP_PASSWORD;
43
-
44
- if (password) {
45
- try {
46
- const params = await sdk.account.getEncryptionParams();
47
- if (!params.setupComplete) {
48
- console.error('[tusky-mcp] Encryption not set up on this account. Skipping encryption unlock.');
49
- return null;
50
- }
51
-
52
- const salt = Buffer.from(params.salt!, 'base64');
53
- const verifier = Buffer.from(params.verifier!, 'base64');
54
- const wrappingKey = deriveMasterKey(password, salt);
55
-
56
- if (!verifyPassphrase(wrappingKey, verifier)) {
57
- console.error('[tusky-mcp] TUSKYDP_PASSWORD is incorrect. Encryption will be unavailable.');
58
- return null;
59
- }
60
-
61
- if (params.encryptedMasterKey) {
62
- return unwrapMasterKey(Buffer.from(params.encryptedMasterKey, 'base64'), wrappingKey);
63
- }
64
- // Legacy accounts where wrapping key IS the master key
65
- return wrappingKey;
66
- } catch (err: any) {
67
- console.error(`[tusky-mcp] Failed to unlock encryption: ${err.message}`);
68
- return null;
69
- }
70
- }
71
-
72
- // Fallback: try existing session file
73
- const sessionKey = loadMasterKey();
74
- if (sessionKey) {
75
- console.error('[tusky-mcp] Using encryption key from existing session.');
76
- return sessionKey;
77
- }
78
-
79
- return null;
80
- }
81
-
82
24
  // ---------------------------------------------------------------------------
83
25
  // Server lifecycle
84
26
  // ---------------------------------------------------------------------------
@@ -107,15 +49,6 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
107
49
  process.exit(1);
108
50
  }
109
51
 
110
- // Unlock encryption (best effort)
111
- const masterKey = await unlockMasterKey(sdk);
112
- if (masterKey) {
113
- console.error('[tusky-mcp] Encryption unlocked — private vault operations are available.');
114
- } else {
115
- console.error('[tusky-mcp] Encryption not unlocked — private vault encrypt/decrypt unavailable.');
116
- console.error('[tusky-mcp] Set TUSKYDP_PASSWORD env var or run `tusky encryption unlock` first.');
117
- }
118
-
119
52
  // Load Sui keypair for SEAL shared vault operations (best effort)
120
53
  const suiKeypair = getSuiKeypair();
121
54
  if (suiKeypair) {
@@ -127,8 +60,6 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
127
60
  // Build context
128
61
  const ctx: McpContext = {
129
62
  sdk,
130
- getMasterKey: () => masterKey,
131
- isEncryptionReady: () => masterKey !== null,
132
63
  getSuiKeypair: () => suiKeypair,
133
64
  isSealReady: () => suiKeypair !== null,
134
65
  };
@@ -9,7 +9,7 @@ import { wrapToolError } from './helpers.js';
9
9
  export function registerAccountTools(server: McpServer, ctx: McpContext) {
10
10
  server.tool(
11
11
  'tusky_account_info',
12
- 'Get account information including email, plan, storage usage, and encryption status',
12
+ 'Get account information including email, plan, and storage usage',
13
13
  {},
14
14
  async () => {
15
15
  try {
@@ -24,8 +24,6 @@ export function registerAccountTools(server: McpServer, ctx: McpContext) {
24
24
  storageLimit: account.storageLimitFormatted,
25
25
  storageUsedBytes: account.storageUsedBytes,
26
26
  storageLimitBytes: account.storageLimitBytes,
27
- encryptionSetup: account.encryptionSetupComplete,
28
- encryptionUnlocked: ctx.isEncryptionReady(),
29
27
  createdAt: account.createdAt,
30
28
  };
31
29
 
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * MCP tools — File operations
3
3
  *
4
- * Handles upload with client-side encryption for private vaults,
5
- * SEAL encryption for shared vaults, download with decryption and
6
- * rehydration polling for cold files.
4
+ * Handles upload with SEAL encryption for shared vaults, download
5
+ * with decryption and rehydration polling for cold files.
7
6
  */
8
7
 
9
8
  import { z } from 'zod';
@@ -13,7 +12,6 @@ import { basename, resolve, join } from 'path';
13
12
  import { lookup } from 'mime-types';
14
13
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
15
14
  import type { McpContext } from '../context.js';
16
- import { encryptBuffer, decryptBuffer, encryptMetadata, decryptMetadata } from '../../crypto.js';
17
15
  import { isSealConfigured, sealEncrypt, sealDecrypt, sealEncryptMetadata, sealDecryptMetadata } from '../../seal.js';
18
16
  import { wrapToolError } from './helpers.js';
19
17
 
@@ -79,10 +77,9 @@ async function fetchAndDecryptFile(fileId: string, ctx: McpContext) {
79
77
  const arrayBuf = await response.arrayBuffer();
80
78
  let fileBuffer: Buffer = Buffer.from(arrayBuf as ArrayBuffer);
81
79
 
82
- // Decrypt if needed — dispatch on encryption type
80
+ // Decrypt if needed — SEAL-encrypted (shared vault)
83
81
  if (encryption?.encrypted) {
84
82
  if ('type' in encryption && encryption.type === 'seal') {
85
- // SEAL-encrypted (shared vault)
86
83
  const keypair = ctx.getSuiKeypair();
87
84
  if (!keypair) {
88
85
  throw new Error(
@@ -94,23 +91,6 @@ async function fetchAndDecryptFile(fileId: string, ctx: McpContext) {
94
91
  const vault = await ctx.sdk.vaults.get(fileInfo.vaultId);
95
92
  const decrypted = await sealDecrypt(new Uint8Array(fileBuffer), vault, keypair);
96
93
  fileBuffer = Buffer.from(decrypted);
97
- } else {
98
- // Passphrase-encrypted (private vault)
99
- const masterKey = ctx.getMasterKey();
100
- if (!masterKey) {
101
- throw new Error(
102
- 'Encryption passphrase required to decrypt this file. ' +
103
- 'Set the TUSKYDP_PASSWORD environment variable in your MCP server config.',
104
- );
105
- }
106
- const enc = encryption as { type: 'passphrase'; encrypted: true; wrappedKey: string; iv: string; plaintextChecksumSha256: string | null };
107
- fileBuffer = decryptBuffer(
108
- fileBuffer,
109
- enc.wrappedKey,
110
- enc.iv,
111
- masterKey,
112
- enc.plaintextChecksumSha256 ?? undefined,
113
- );
114
94
  }
115
95
  }
116
96
 
@@ -119,22 +99,16 @@ async function fetchAndDecryptFile(fileId: string, ctx: McpContext) {
119
99
 
120
100
  /**
121
101
  * Resolve the real filename from a FileStatusResponse.
122
- * Decrypts encryptedName (private vaults) or encryptedMetadata (shared vaults) when present.
102
+ * Decrypts encryptedMetadata (shared vaults) when present.
123
103
  * Falls back to the stored placeholder name on failure.
124
104
  */
125
105
  async function resolveFilename(
126
- fileInfo: { name: string; nameEncrypted?: boolean; encryptedName?: string | null; encryptedMetadata?: string | null; fileId?: string },
106
+ fileInfo: { name: string; nameEncrypted?: boolean; encryptedMetadata?: string | null; fileId?: string },
127
107
  ctx: McpContext,
128
108
  ): Promise<string> {
129
109
  if (!fileInfo.nameEncrypted) return fileInfo.name;
130
110
  try {
131
- if (fileInfo.encryptedName) {
132
- const masterKey = ctx.getMasterKey();
133
- if (masterKey) {
134
- const meta = decryptMetadata(fileInfo.encryptedName, masterKey);
135
- return meta.n;
136
- }
137
- } else if (fileInfo.encryptedMetadata && fileInfo.fileId) {
111
+ if (fileInfo.encryptedMetadata && fileInfo.fileId) {
138
112
  const keypair = ctx.getSuiKeypair();
139
113
  if (keypair) {
140
114
  const fileObj = await ctx.sdk.files.get(fileInfo.fileId);
@@ -150,7 +124,7 @@ async function resolveFilename(
150
124
  }
151
125
 
152
126
  /**
153
- * Encrypt (if private vault) and upload a buffer to Tusky.
127
+ * Encrypt (if shared vault) and upload a buffer to Tusky.
154
128
  * Shared by tusky_file_upload (from disk) and tusky_file_create (from content).
155
129
  */
156
130
  async function encryptAndUpload(
@@ -162,40 +136,16 @@ async function encryptAndUpload(
162
136
  ctx: McpContext,
163
137
  ) {
164
138
  const vault = await ctx.sdk.vaults.get(vaultId);
165
- const isPrivate = vault.visibility === 'private';
166
139
  const isShared = vault.visibility === 'shared' && isSealConfigured(vault);
167
140
 
168
141
  let uploadBody: Buffer;
169
142
  let encryptionMeta: {
170
- wrappedKey?: string;
171
- encryptionIv?: string;
172
- plaintextSizeBytes?: number;
173
- plaintextChecksumSha256?: string;
174
143
  sealIdentity?: string;
175
144
  sealEncryptedObject?: string;
176
- encryptedName?: string;
177
145
  encryptedMetadata?: string;
178
146
  } = {};
179
147
 
180
- if (isPrivate) {
181
- const masterKey = ctx.getMasterKey();
182
- if (!masterKey) {
183
- throw new Error(
184
- 'Encryption passphrase required for private vault uploads. ' +
185
- 'Set the TUSKYDP_PASSWORD environment variable in your MCP server config.',
186
- );
187
- }
188
-
189
- const { ciphertext, wrappedKey, iv, plaintextChecksum } = encryptBuffer(fileBuffer, masterKey);
190
- uploadBody = ciphertext;
191
- encryptionMeta = {
192
- wrappedKey,
193
- encryptionIv: iv,
194
- plaintextSizeBytes: fileBuffer.length,
195
- plaintextChecksumSha256: plaintextChecksum,
196
- encryptedName: encryptMetadata(fileName, mimeType, masterKey),
197
- };
198
- } else if (isShared) {
148
+ if (isShared) {
199
149
  const keypair = ctx.getSuiKeypair();
200
150
  if (!keypair) {
201
151
  throw new Error(
@@ -210,7 +160,6 @@ async function encryptAndUpload(
210
160
  encryptionMeta = {
211
161
  sealIdentity: sealResult.sealIdentity,
212
162
  sealEncryptedObject: sealResult.sealEncryptedObject,
213
- plaintextSizeBytes: fileBuffer.length,
214
163
  encryptedMetadata: await sealEncryptMetadata(fileName, mimeType, vault, fileNonce),
215
164
  };
216
165
  } else {
@@ -241,7 +190,7 @@ async function encryptAndUpload(
241
190
  // Confirm upload
242
191
  const file = await ctx.sdk.files.confirmUpload(fileId);
243
192
 
244
- return { file, isPrivate, isShared, uploadedSizeBytes: uploadBody.length };
193
+ return { file, isShared, uploadedSizeBytes: uploadBody.length };
245
194
  }
246
195
 
247
196
  // ---------------------------------------------------------------------------
@@ -252,7 +201,7 @@ export function registerFileTools(server: McpServer, ctx: McpContext) {
252
201
  // ── Upload file from disk ──────────────────────────────────────────────
253
202
  server.tool(
254
203
  'tusky_file_upload',
255
- 'Upload a file from a local filesystem path to a vault. Handles encryption transparently for private vaults. Use tusky_file_create instead if you want to upload content directly without a file on disk.',
204
+ 'Upload a file from a local filesystem path to a vault. Handles SEAL encryption transparently for shared vaults. Use tusky_file_create instead if you want to upload content directly without a file on disk.',
256
205
  {
257
206
  filePath: z.string().describe('Absolute or relative path to the local file to upload'),
258
207
  vaultId: z.string().describe('Target vault ID'),
@@ -277,7 +226,7 @@ export function registerFileTools(server: McpServer, ctx: McpContext) {
277
226
  const fileName = basename(resolvedPath);
278
227
  const mimeType = lookup(resolvedPath) || 'application/octet-stream';
279
228
 
280
- const { file, isPrivate, isShared, uploadedSizeBytes } = await encryptAndUpload(
229
+ const { file, isShared, uploadedSizeBytes } = await encryptAndUpload(
281
230
  fileBuffer, fileName, mimeType, vaultId, folderId, ctx,
282
231
  );
283
232
 
@@ -285,8 +234,8 @@ export function registerFileTools(server: McpServer, ctx: McpContext) {
285
234
  content: [{ type: 'text' as const, text: JSON.stringify({
286
235
  ...file,
287
236
  localPath: resolvedPath,
288
- encrypted: isPrivate || isShared,
289
- encryptionType: isPrivate ? 'passphrase' : isShared ? 'seal' : 'none',
237
+ encrypted: isShared,
238
+ encryptionType: isShared ? 'seal' : 'none',
290
239
  uploadedSizeBytes,
291
240
  }, null, 2) }],
292
241
  };
@@ -299,7 +248,7 @@ export function registerFileTools(server: McpServer, ctx: McpContext) {
299
248
  // ── Create file from content ─────────────────────────────────────────
300
249
  server.tool(
301
250
  'tusky_file_create',
302
- 'Create a file in a vault from raw content (text or base64-encoded binary). Use this when you want to upload content directly without needing a file on disk. Handles encryption transparently for private vaults.',
251
+ 'Create a file in a vault from raw content (text or base64-encoded binary). Use this when you want to upload content directly without needing a file on disk. Handles SEAL encryption transparently for shared vaults.',
303
252
  {
304
253
  name: z.string().describe('File name including extension (e.g. "report.md", "data.json", "image.png")'),
305
254
  content: z.string().describe('File content: plain text for text files, or base64-encoded string for binary files'),
@@ -318,15 +267,15 @@ export function registerFileTools(server: McpServer, ctx: McpContext) {
318
267
 
319
268
  const mimeType = lookup(name) || 'application/octet-stream';
320
269
 
321
- const { file, isPrivate, isShared, uploadedSizeBytes } = await encryptAndUpload(
270
+ const { file, isShared, uploadedSizeBytes } = await encryptAndUpload(
322
271
  fileBuffer, name, mimeType, vaultId, folderId, ctx,
323
272
  );
324
273
 
325
274
  return {
326
275
  content: [{ type: 'text' as const, text: JSON.stringify({
327
276
  ...file,
328
- encrypted: isPrivate || isShared,
329
- encryptionType: isPrivate ? 'passphrase' : isShared ? 'seal' : 'none',
277
+ encrypted: isShared,
278
+ encryptionType: isShared ? 'seal' : 'none',
330
279
  uploadedSizeBytes,
331
280
  }, null, 2) }],
332
281
  };
@@ -339,7 +288,7 @@ export function registerFileTools(server: McpServer, ctx: McpContext) {
339
288
  // ── Download file (to disk) ────────────────────────────────────────────
340
289
  server.tool(
341
290
  'tusky_file_download',
342
- 'Download a file by ID and save it to a local path on disk. Handles decryption for private vaults and rehydration for cold files. Use tusky_file_read instead if you just want the file content returned directly.',
291
+ 'Download a file by ID and save it to a local path on disk. Handles SEAL decryption for shared vaults and rehydration for cold files. Use tusky_file_read instead if you just want the file content returned directly.',
343
292
  {
344
293
  fileId: z.string().describe('File ID to download'),
345
294
  outputPath: z.string().describe('Local path to save the file (must be writable)'),
@@ -375,7 +324,7 @@ export function registerFileTools(server: McpServer, ctx: McpContext) {
375
324
  // ── Read file content (inline) ───────────────────────────────────────
376
325
  server.tool(
377
326
  'tusky_file_read',
378
- 'Read a file\'s content and return it directly. For text files the content is returned as text. For binary files it is returned as base64. Handles decryption for private vaults and rehydration for cold files transparently. Prefer this over tusky_file_download when you need to inspect file content.',
327
+ 'Read a file\'s content and return it directly. For text files the content is returned as text. For binary files it is returned as base64. Handles SEAL decryption for shared vaults and rehydration for cold files transparently. Prefer this over tusky_file_download when you need to inspect file content.',
379
328
  {
380
329
  fileId: z.string().describe('File ID to read'),
381
330
  },
@@ -15,8 +15,8 @@ export function registerVaultTools(server: McpServer, ctx: McpContext) {
15
15
  {
16
16
  name: z.string().describe('Vault name'),
17
17
  description: z.string().optional().describe('Vault description'),
18
- visibility: z.enum(['public', 'private', 'shared']).optional().describe(
19
- 'Vault visibility. "private" uses passphrase-based E2E encryption. "shared" uses SEAL protocol for multi-party access. Defaults to "private".',
18
+ visibility: z.enum(['public', 'shared']).optional().describe(
19
+ 'Vault visibility. "shared" uses SEAL protocol for multi-party access. Defaults to "public".',
20
20
  ),
21
21
  },
22
22
  async ({ name, description, visibility }) => {
@@ -24,7 +24,7 @@ export function registerVaultTools(server: McpServer, ctx: McpContext) {
24
24
  const vault = await ctx.sdk.vaults.create({
25
25
  name,
26
26
  description,
27
- visibility: visibility ?? 'private',
27
+ visibility: visibility ?? 'public',
28
28
  });
29
29
  return {
30
30
  content: [{ type: 'text' as const, text: JSON.stringify(vault, null, 2) }],
@@ -12,7 +12,6 @@ export interface FileItem {
12
12
  id: string;
13
13
  name: string;
14
14
  sizeBytes: number;
15
- plaintextSizeBytes: number | null;
16
15
  mimeType: string;
17
16
  status: string;
18
17
  createdAt: string;
@@ -118,7 +117,6 @@ export class FilesPanel {
118
117
  id: f.id,
119
118
  name: f.name,
120
119
  sizeBytes: f.sizeBytes || 0,
121
- plaintextSizeBytes: f.plaintextSizeBytes || null,
122
120
  mimeType: f.mimeType || '',
123
121
  status: f.status || 'unknown',
124
122
  createdAt: f.createdAt || '',
@@ -183,7 +181,7 @@ export class FilesPanel {
183
181
  }
184
182
  // file
185
183
  const f = item.file;
186
- const size = formatBytes(f.plaintextSizeBytes ?? f.sizeBytes);
184
+ const size = formatBytes(f.sizeBytes);
187
185
  const status = f.status;
188
186
  const statusTagged = `${statusColor(f.status)}${f.status}${statusColorClose(f.status)}`;
189
187
  const date = f.createdAt ? formatDate(f.createdAt) : '';
package/src/tui/index.ts CHANGED
@@ -16,8 +16,6 @@ function collectFiles(dir: string): string[] {
16
16
  import { lookup } from 'mime-types';
17
17
  import { createSDKClient } from '../sdk.js';
18
18
  import { cliConfig, getApiUrl } from '../config.js';
19
- import { encryptBuffer, decryptBuffer } from '../crypto.js';
20
- import { loadMasterKey } from '../lib/keyring.js';
21
19
  import { isSealConfigured, sealEncrypt, sealDecrypt, getSuiKeypair } from '../seal.js';
22
20
  import { showAuthScreen } from './auth-screen.js';
23
21
  import { VaultsPanel, VaultItem } from './vaults-panel.js';
@@ -135,12 +133,12 @@ export async function launchTui() {
135
133
  setFocus(currentFocus);
136
134
  return;
137
135
  }
138
- const visIdx = await selectDialog(screen, 'Visibility', ['private', 'public', 'shared']);
136
+ const visIdx = await selectDialog(screen, 'Visibility', ['public', 'shared']);
139
137
  if (visIdx < 0) {
140
138
  setFocus(currentFocus);
141
139
  return;
142
140
  }
143
- const visMap = ['private', 'public', 'shared'] as const;
141
+ const visMap = ['public', 'shared'] as const;
144
142
  const visibility = visMap[visIdx];
145
143
 
146
144
  try {
@@ -225,18 +223,7 @@ export async function launchTui() {
225
223
 
226
224
  try {
227
225
  const vault = await sdk.vaults.get(vaultId);
228
- const isPrivate = vault.visibility === 'private';
229
226
  const isShared = vault.visibility === 'shared' && isSealConfigured(vault);
230
- let masterKey: Buffer | null = null;
231
-
232
- if (isPrivate) {
233
- masterKey = loadMasterKey();
234
- if (!masterKey) {
235
- showError(screen, 'Encryption session not unlocked. Run: tusky encryption unlock');
236
- setFocus(currentFocus);
237
- return;
238
- }
239
- }
240
227
 
241
228
  if (isShared) {
242
229
  const keypair = getSuiKeypair();
@@ -262,24 +249,11 @@ export async function launchTui() {
262
249
 
263
250
  let uploadBody: Buffer;
264
251
  let encryptionMeta: {
265
- wrappedKey?: string;
266
- encryptionIv?: string;
267
- plaintextSizeBytes?: number;
268
- plaintextChecksumSha256?: string;
269
252
  sealIdentity?: string;
270
253
  sealEncryptedObject?: string;
271
254
  } = {};
272
255
 
273
- if (isPrivate && masterKey) {
274
- const { ciphertext, wrappedKey, iv, plaintextChecksum } = encryptBuffer(fileBuffer, masterKey);
275
- uploadBody = ciphertext;
276
- encryptionMeta = {
277
- wrappedKey,
278
- encryptionIv: iv,
279
- plaintextSizeBytes: stat.size,
280
- plaintextChecksumSha256: plaintextChecksum,
281
- };
282
- } else if (isShared) {
256
+ if (isShared) {
283
257
  statusBar.setHints(`SEAL encrypting ${successCount + 1}/${total}: ${fileName}...`);
284
258
  screen.render();
285
259
  const fileNonce = randomUUID();
@@ -288,7 +262,6 @@ export async function launchTui() {
288
262
  encryptionMeta = {
289
263
  sealIdentity: sealResult.sealIdentity,
290
264
  sealEncryptedObject: sealResult.sealEncryptedObject,
291
- plaintextSizeBytes: stat.size,
292
265
  };
293
266
  } else {
294
267
  uploadBody = fileBuffer;
@@ -315,7 +288,7 @@ export async function launchTui() {
315
288
  successCount++;
316
289
  }
317
290
 
318
- const label = isPrivate ? 'private' : isShared ? 'shared/SEAL' : 'public';
291
+ const label = isShared ? 'shared/SEAL' : 'public';
319
292
  showMessage(screen, `Uploaded ${successCount} file(s) [${label}]`);
320
293
  await filesPanel.loadForVault(vaultId, filesPanel.getCurrentVaultName());
321
294
  await vaultsPanel.load();
@@ -378,7 +351,7 @@ export async function launchTui() {
378
351
  const arrayBuf = await response.arrayBuffer();
379
352
  let fileBuffer: Buffer = Buffer.from(new Uint8Array(arrayBuf));
380
353
 
381
- // Decrypt if needed
354
+ // Decrypt if needed — SEAL-encrypted (shared vault)
382
355
  if (encryption?.encrypted) {
383
356
  if ('type' in encryption && encryption.type === 'seal') {
384
357
  statusBar.setHints(`SEAL decrypting "${file.name}"...`);
@@ -393,23 +366,6 @@ export async function launchTui() {
393
366
  const vault = await sdk.vaults.get(fileInfo.vaultId);
394
367
  const decrypted = await sealDecrypt(new Uint8Array(fileBuffer), vault, keypair);
395
368
  fileBuffer = Buffer.from(decrypted);
396
- } else {
397
- statusBar.setHints(`Decrypting "${file.name}"...`);
398
- screen.render();
399
- const masterKey = loadMasterKey();
400
- if (!masterKey) {
401
- showError(screen, 'Encryption not unlocked. Run: tusky encryption unlock');
402
- setFocus(currentFocus);
403
- return;
404
- }
405
- const enc = encryption as { type: 'passphrase'; encrypted: true; wrappedKey: string; iv: string; plaintextChecksumSha256: string | null };
406
- fileBuffer = decryptBuffer(
407
- fileBuffer,
408
- enc.wrappedKey,
409
- enc.iv,
410
- masterKey,
411
- enc.plaintextChecksumSha256 ?? undefined,
412
- );
413
369
  }
414
370
  }
415
371
 
@@ -542,7 +498,7 @@ export async function launchTui() {
542
498
  ['Name', file.name],
543
499
  ['ID', file.id],
544
500
  ['MIME', file.mimeType],
545
- ['Size', formatBytes(file.plaintextSizeBytes ?? file.sizeBytes)],
501
+ ['Size', formatBytes(file.sizeBytes)],
546
502
  ['Status', `${open}${file.status}${close}`],
547
503
  ['Encrypted', file.encrypted ? 'Yes' : 'No'],
548
504
  ['Uploaded', file.createdAt ? formatDate(file.createdAt) : 'N/A'],
@@ -647,7 +603,7 @@ export async function launchTui() {
647
603
  ' Enter Open folder / view details',
648
604
  '',
649
605
  '{bold}Actions{/bold}',
650
- ' n Create new vault (private/public/shared)',
606
+ ' n Create new vault (public/shared)',
651
607
  ' f Create folder in current vault',
652
608
  ' u Upload file to current vault/folder',
653
609
  ' d Download selected file',
@@ -77,10 +77,7 @@ export async function showOverview(
77
77
  const vaultRows = vaults.map((v) => {
78
78
  let vis: string;
79
79
  let visTag: string;
80
- if (v.visibility === 'private') {
81
- vis = 'private';
82
- visTag = '{yellow-fg}private{/yellow-fg}';
83
- } else if (v.visibility === 'shared') {
80
+ if (v.visibility === 'shared') {
84
81
  vis = 'shared';
85
82
  visTag = '{magenta-fg}shared{/magenta-fg}';
86
83
  } else {
@@ -119,7 +116,7 @@ export async function showOverview(
119
116
  // Calculate column widths for files table
120
117
  const fileRows = files.map((f) => ({
121
118
  name: f.name || '',
122
- size: formatBytes(f.plaintextSizeBytes ?? f.sizeBytes ?? 0),
119
+ size: formatBytes(f.sizeBytes ?? 0),
123
120
  status: f.status || '',
124
121
  statusTag: `${statusColor(f.status)}${f.status}${statusColorClose(f.status)}`,
125
122
  date: f.createdAt ? formatDate(f.createdAt) : 'N/A',
@@ -78,7 +78,7 @@ export async function showTrashScreen(
78
78
  id: f.id,
79
79
  type: 'file',
80
80
  name: f.name,
81
- size: f.plaintextSizeBytes || f.sizeBytes,
81
+ size: f.sizeBytes,
82
82
  deletedAt: f.deletedAt || '',
83
83
  });
84
84
  }
@@ -1,15 +0,0 @@
1
- /**
2
- * `tusky decrypt` — Decrypt a file downloaded from Walrus.
3
- *
4
- * Works in two modes:
5
- * 1. With --export <manifest.json> — reads wrappedKey/iv from the export
6
- * manifest, derives master key from passphrase + salt. Fully offline.
7
- * 2. Without --export — fetches encryption params from the Tusky API and
8
- * looks up the file metadata by ID. Requires API access.
9
- *
10
- * In both modes, the passphrase is resolved from:
11
- * --passphrase flag > TUSKYDP_PASSWORD env var > interactive prompt
12
- */
13
- import type { Command } from 'commander';
14
- export declare function registerDecryptCommand(program: Command): void;
15
- //# sourceMappingURL=decrypt.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../../src/commands/decrypt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsFzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,QAiNtD"}