@tuskydp/cli 0.1.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 (153) hide show
  1. package/dist/bin/tuskydp.d.ts +3 -0
  2. package/dist/bin/tuskydp.d.ts.map +1 -0
  3. package/dist/bin/tuskydp.js +3 -0
  4. package/dist/bin/tuskydp.js.map +1 -0
  5. package/dist/src/client.d.ts +120 -0
  6. package/dist/src/client.d.ts.map +1 -0
  7. package/dist/src/client.js +152 -0
  8. package/dist/src/client.js.map +1 -0
  9. package/dist/src/commands/account.d.ts +3 -0
  10. package/dist/src/commands/account.d.ts.map +1 -0
  11. package/dist/src/commands/account.js +66 -0
  12. package/dist/src/commands/account.js.map +1 -0
  13. package/dist/src/commands/auth.d.ts +3 -0
  14. package/dist/src/commands/auth.d.ts.map +1 -0
  15. package/dist/src/commands/auth.js +171 -0
  16. package/dist/src/commands/auth.js.map +1 -0
  17. package/dist/src/commands/decrypt.d.ts +15 -0
  18. package/dist/src/commands/decrypt.d.ts.map +1 -0
  19. package/dist/src/commands/decrypt.js +224 -0
  20. package/dist/src/commands/decrypt.js.map +1 -0
  21. package/dist/src/commands/download.d.ts +5 -0
  22. package/dist/src/commands/download.d.ts.map +1 -0
  23. package/dist/src/commands/download.js +67 -0
  24. package/dist/src/commands/download.js.map +1 -0
  25. package/dist/src/commands/encryption.d.ts +3 -0
  26. package/dist/src/commands/encryption.d.ts.map +1 -0
  27. package/dist/src/commands/encryption.js +254 -0
  28. package/dist/src/commands/encryption.js.map +1 -0
  29. package/dist/src/commands/export.d.ts +70 -0
  30. package/dist/src/commands/export.d.ts.map +1 -0
  31. package/dist/src/commands/export.js +178 -0
  32. package/dist/src/commands/export.js.map +1 -0
  33. package/dist/src/commands/files.d.ts +3 -0
  34. package/dist/src/commands/files.d.ts.map +1 -0
  35. package/dist/src/commands/files.js +179 -0
  36. package/dist/src/commands/files.js.map +1 -0
  37. package/dist/src/commands/mcp.d.ts +8 -0
  38. package/dist/src/commands/mcp.d.ts.map +1 -0
  39. package/dist/src/commands/mcp.js +178 -0
  40. package/dist/src/commands/mcp.js.map +1 -0
  41. package/dist/src/commands/rehydrate.d.ts +5 -0
  42. package/dist/src/commands/rehydrate.d.ts.map +1 -0
  43. package/dist/src/commands/rehydrate.js +27 -0
  44. package/dist/src/commands/rehydrate.js.map +1 -0
  45. package/dist/src/commands/tui.d.ts +3 -0
  46. package/dist/src/commands/tui.d.ts.map +1 -0
  47. package/dist/src/commands/tui.js +10 -0
  48. package/dist/src/commands/tui.js.map +1 -0
  49. package/dist/src/commands/upload.d.ts +6 -0
  50. package/dist/src/commands/upload.d.ts.map +1 -0
  51. package/dist/src/commands/upload.js +121 -0
  52. package/dist/src/commands/upload.js.map +1 -0
  53. package/dist/src/commands/vault.d.ts +3 -0
  54. package/dist/src/commands/vault.d.ts.map +1 -0
  55. package/dist/src/commands/vault.js +118 -0
  56. package/dist/src/commands/vault.js.map +1 -0
  57. package/dist/src/config.d.ts +15 -0
  58. package/dist/src/config.d.ts.map +1 -0
  59. package/dist/src/config.js +26 -0
  60. package/dist/src/config.js.map +1 -0
  61. package/dist/src/crypto.d.ts +16 -0
  62. package/dist/src/crypto.d.ts.map +1 -0
  63. package/dist/src/crypto.js +95 -0
  64. package/dist/src/crypto.js.map +1 -0
  65. package/dist/src/index.d.ts +2 -0
  66. package/dist/src/index.d.ts.map +1 -0
  67. package/dist/src/index.js +59 -0
  68. package/dist/src/index.js.map +1 -0
  69. package/dist/src/lib/keyring.d.ts +4 -0
  70. package/dist/src/lib/keyring.d.ts.map +1 -0
  71. package/dist/src/lib/keyring.js +51 -0
  72. package/dist/src/lib/keyring.js.map +1 -0
  73. package/dist/src/lib/output.d.ts +6 -0
  74. package/dist/src/lib/output.d.ts.map +1 -0
  75. package/dist/src/lib/output.js +37 -0
  76. package/dist/src/lib/output.js.map +1 -0
  77. package/dist/src/lib/progress.d.ts +2 -0
  78. package/dist/src/lib/progress.d.ts.map +1 -0
  79. package/dist/src/lib/progress.js +5 -0
  80. package/dist/src/lib/progress.js.map +1 -0
  81. package/dist/src/lib/resolve.d.ts +3 -0
  82. package/dist/src/lib/resolve.d.ts.map +1 -0
  83. package/dist/src/lib/resolve.js +25 -0
  84. package/dist/src/lib/resolve.js.map +1 -0
  85. package/dist/src/mcp/context.d.ts +19 -0
  86. package/dist/src/mcp/context.d.ts.map +1 -0
  87. package/dist/src/mcp/context.js +8 -0
  88. package/dist/src/mcp/context.js.map +1 -0
  89. package/dist/src/mcp/server.d.ts +13 -0
  90. package/dist/src/mcp/server.d.ts.map +1 -0
  91. package/dist/src/mcp/server.js +113 -0
  92. package/dist/src/mcp/server.js.map +1 -0
  93. package/dist/src/mcp/tools/account.d.ts +7 -0
  94. package/dist/src/mcp/tools/account.d.ts.map +1 -0
  95. package/dist/src/mcp/tools/account.js +31 -0
  96. package/dist/src/mcp/tools/account.js.map +1 -0
  97. package/dist/src/mcp/tools/files.d.ts +10 -0
  98. package/dist/src/mcp/tools/files.d.ts.map +1 -0
  99. package/dist/src/mcp/tools/files.js +310 -0
  100. package/dist/src/mcp/tools/files.js.map +1 -0
  101. package/dist/src/mcp/tools/folders.d.ts +7 -0
  102. package/dist/src/mcp/tools/folders.d.ts.map +1 -0
  103. package/dist/src/mcp/tools/folders.js +67 -0
  104. package/dist/src/mcp/tools/folders.js.map +1 -0
  105. package/dist/src/mcp/tools/helpers.d.ts +15 -0
  106. package/dist/src/mcp/tools/helpers.d.ts.map +1 -0
  107. package/dist/src/mcp/tools/helpers.js +25 -0
  108. package/dist/src/mcp/tools/helpers.js.map +1 -0
  109. package/dist/src/mcp/tools/trash.d.ts +7 -0
  110. package/dist/src/mcp/tools/trash.d.ts.map +1 -0
  111. package/dist/src/mcp/tools/trash.js +46 -0
  112. package/dist/src/mcp/tools/trash.js.map +1 -0
  113. package/dist/src/mcp/tools/vaults.d.ts +7 -0
  114. package/dist/src/mcp/tools/vaults.d.ts.map +1 -0
  115. package/dist/src/mcp/tools/vaults.js +85 -0
  116. package/dist/src/mcp/tools/vaults.js.map +1 -0
  117. package/dist/src/sdk.d.ts +53 -0
  118. package/dist/src/sdk.d.ts.map +1 -0
  119. package/dist/src/sdk.js +88 -0
  120. package/dist/src/sdk.js.map +1 -0
  121. package/dist/src/tui/auth-screen.d.ts +6 -0
  122. package/dist/src/tui/auth-screen.d.ts.map +1 -0
  123. package/dist/src/tui/auth-screen.js +165 -0
  124. package/dist/src/tui/auth-screen.js.map +1 -0
  125. package/dist/src/tui/dialogs.d.ts +10 -0
  126. package/dist/src/tui/dialogs.d.ts.map +1 -0
  127. package/dist/src/tui/dialogs.js +303 -0
  128. package/dist/src/tui/dialogs.js.map +1 -0
  129. package/dist/src/tui/files-panel.d.ts +34 -0
  130. package/dist/src/tui/files-panel.d.ts.map +1 -0
  131. package/dist/src/tui/files-panel.js +135 -0
  132. package/dist/src/tui/files-panel.js.map +1 -0
  133. package/dist/src/tui/helpers.d.ts +38 -0
  134. package/dist/src/tui/helpers.d.ts.map +1 -0
  135. package/dist/src/tui/helpers.js +187 -0
  136. package/dist/src/tui/helpers.js.map +1 -0
  137. package/dist/src/tui/index.d.ts +2 -0
  138. package/dist/src/tui/index.d.ts.map +1 -0
  139. package/dist/src/tui/index.js +382 -0
  140. package/dist/src/tui/index.js.map +1 -0
  141. package/dist/src/tui/overview.d.ts +4 -0
  142. package/dist/src/tui/overview.d.ts.map +1 -0
  143. package/dist/src/tui/overview.js +136 -0
  144. package/dist/src/tui/overview.js.map +1 -0
  145. package/dist/src/tui/status-bar.d.ts +18 -0
  146. package/dist/src/tui/status-bar.d.ts.map +1 -0
  147. package/dist/src/tui/status-bar.js +54 -0
  148. package/dist/src/tui/status-bar.js.map +1 -0
  149. package/dist/src/tui/vaults-panel.d.ts +26 -0
  150. package/dist/src/tui/vaults-panel.d.ts.map +1 -0
  151. package/dist/src/tui/vaults-panel.js +118 -0
  152. package/dist/src/tui/vaults-panel.js.map +1 -0
  153. package/package.json +42 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `tusky export` — Export file metadata for disaster recovery.
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.
7
+ *
8
+ * The export contains everything needed to:
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
12
+ */
13
+ import type { Command } from 'commander';
14
+ export interface ExportedFile {
15
+ fileId: string;
16
+ name: string;
17
+ mimeType: string;
18
+ sizeBytes: number;
19
+ vaultId: string;
20
+ vaultName: string;
21
+ vaultVisibility: string;
22
+ folderId: string | null;
23
+ status: string;
24
+ /** Walrus content-addressed blob ID — use to fetch from any Walrus aggregator */
25
+ walrusBlobId: string | null;
26
+ /** Sui object ID — required for epoch renewal via Walrus CLI */
27
+ walrusBlobObjectId: string | null;
28
+ /** Epoch when Walrus storage expires (renew before this) */
29
+ walrusEpochEnd: string | null;
30
+ /** Whether the file is client-side encrypted */
31
+ encrypted: boolean;
32
+ /** AES-256-GCM file key, wrapped with the master key (base64) */
33
+ wrappedKey: string | null;
34
+ /** AES-256-GCM initialization vector (base64) */
35
+ encryptionIv: string | null;
36
+ /** SHA-256 of the plaintext — for integrity verification after decryption */
37
+ plaintextChecksumSha256: string | null;
38
+ /** Original plaintext size in bytes (before encryption) */
39
+ plaintextSizeBytes: number | null;
40
+ createdAt: string;
41
+ }
42
+ export interface ExportManifest {
43
+ version: 1;
44
+ exportedAt: string;
45
+ account: {
46
+ id: string;
47
+ email: string;
48
+ plan: string;
49
+ };
50
+ /** Account encryption params — needed to derive master key from passphrase */
51
+ encryption: {
52
+ setupComplete: boolean;
53
+ /** PBKDF2 salt (base64) */
54
+ salt: string | null;
55
+ /** HMAC verifier for passphrase validation (base64) */
56
+ verifier: string | null;
57
+ /** Master key wrapped with passphrase-derived key (base64) */
58
+ encryptedMasterKey: string | null;
59
+ };
60
+ walrusNetwork: string;
61
+ walrusAggregatorUrl: string;
62
+ totalFiles: number;
63
+ totalVaults: number;
64
+ files: ExportedFile[];
65
+ recovery: {
66
+ instructions: string[];
67
+ };
68
+ }
69
+ export declare function registerExportCommand(program: Command): void;
70
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/commands/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQzC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gEAAgE;IAChE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,4DAA4D;IAC5D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iDAAiD;IACjD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,2DAA2D;IAC3D,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,8EAA8E;IAC9E,UAAU,EAAE;QACV,aAAa,EAAE,OAAO,CAAC;QACvB,2BAA2B;QAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,uDAAuD;QACvD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,8DAA8D;QAC9D,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;KACnC,CAAC;IACF,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE;QACR,YAAY,EAAE,MAAM,EAAE,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,QA4KrD"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * `tusky export` — Export file metadata for disaster recovery.
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.
7
+ *
8
+ * The export contains everything needed to:
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
12
+ */
13
+ import { writeFileSync } from 'fs';
14
+ import { resolve } from 'path';
15
+ import chalk from 'chalk';
16
+ import { getApiUrl, getApiKey } from '../config.js';
17
+ import { createSDKClient } from '../sdk.js';
18
+ import { createSpinner } from '../lib/progress.js';
19
+ export function registerExportCommand(program) {
20
+ program
21
+ .command('export')
22
+ .description('Export file metadata for disaster recovery (Walrus blob IDs, encryption keys)')
23
+ .option('-o, --output <path>', 'Output file path', 'tusky-export.json')
24
+ .option('--vault <vaultId>', 'Export only files from a specific vault')
25
+ .action(async (options) => {
26
+ const apiUrl = getApiUrl(program.opts().apiUrl);
27
+ const apiKey = getApiKey(program.opts().apiKey);
28
+ const sdk = createSDKClient(apiUrl, apiKey);
29
+ const spinner = createSpinner('Fetching account info...');
30
+ spinner.start();
31
+ try {
32
+ // Fetch account + encryption params
33
+ const account = await sdk.account.get();
34
+ const encryptionParams = await sdk.account.getEncryptionParams();
35
+ // Fetch all vaults
36
+ spinner.text = 'Fetching vaults...';
37
+ const allVaults = await sdk.vaults.list();
38
+ const vaults = options.vault
39
+ ? allVaults.filter((v) => v.id === options.vault)
40
+ : allVaults;
41
+ if (vaults.length === 0) {
42
+ spinner.fail(options.vault ? `Vault ${options.vault} not found.` : 'No vaults found.');
43
+ return;
44
+ }
45
+ // Build vault lookup
46
+ const vaultMap = new Map(allVaults.map((v) => [v.id, v]));
47
+ // Fetch all files with pagination
48
+ spinner.text = 'Fetching files...';
49
+ const exportedFiles = [];
50
+ for (const vault of vaults) {
51
+ let cursor;
52
+ do {
53
+ const result = await sdk.files.list({
54
+ vaultId: vault.id,
55
+ limit: 100,
56
+ cursor,
57
+ });
58
+ for (const file of result.files) {
59
+ const v = vaultMap.get(file.vaultId);
60
+ exportedFiles.push({
61
+ fileId: file.id,
62
+ name: file.name,
63
+ mimeType: file.mimeType,
64
+ sizeBytes: file.sizeBytes,
65
+ vaultId: file.vaultId,
66
+ vaultName: v?.name ?? 'unknown',
67
+ vaultVisibility: v?.visibility ?? 'unknown',
68
+ folderId: file.folderId,
69
+ status: file.status,
70
+ walrusBlobId: file.walrusBlobId,
71
+ walrusBlobObjectId: file.walrusBlobObjectId,
72
+ walrusEpochEnd: file.walrusEpochEnd,
73
+ encrypted: file.encrypted,
74
+ wrappedKey: file.wrappedKey,
75
+ encryptionIv: file.encryptionIv,
76
+ plaintextChecksumSha256: file.plaintextChecksumSha256,
77
+ plaintextSizeBytes: file.plaintextSizeBytes,
78
+ createdAt: file.createdAt,
79
+ });
80
+ }
81
+ cursor = result.nextCursor ?? undefined;
82
+ spinner.text = `Fetching files... (${exportedFiles.length} so far)`;
83
+ } while (cursor);
84
+ }
85
+ // Determine Walrus network info
86
+ const tuskyEnv = process.env.TUSKY_ENV || 'production';
87
+ const isMainnet = tuskyEnv === 'production';
88
+ const walrusAggregatorUrl = isMainnet
89
+ ? 'https://aggregator.walrus.space'
90
+ : 'https://aggregator.walrus-testnet.walrus.space';
91
+ // Build manifest
92
+ const manifest = {
93
+ version: 1,
94
+ exportedAt: new Date().toISOString(),
95
+ account: {
96
+ id: account.id,
97
+ email: account.email,
98
+ plan: account.planName,
99
+ },
100
+ encryption: {
101
+ setupComplete: encryptionParams.setupComplete,
102
+ salt: encryptionParams.salt ?? null,
103
+ verifier: encryptionParams.verifier ?? null,
104
+ encryptedMasterKey: encryptionParams.encryptedMasterKey ?? null,
105
+ },
106
+ walrusNetwork: isMainnet ? 'mainnet' : 'testnet',
107
+ walrusAggregatorUrl,
108
+ totalFiles: exportedFiles.length,
109
+ totalVaults: vaults.length,
110
+ files: exportedFiles,
111
+ recovery: {
112
+ instructions: [
113
+ '=== TUSKY DISASTER RECOVERY ===',
114
+ '',
115
+ 'This file contains everything needed to recover your data independently of Tusky.',
116
+ '',
117
+ '--- DOWNLOAD FILES FROM WALRUS ---',
118
+ 'For each file with a walrusBlobId, fetch directly from any Walrus aggregator:',
119
+ ` curl -o <filename> ${walrusAggregatorUrl}/v1/blobs/<walrusBlobId>`,
120
+ ' OR: walrus read <walrusBlobId> -o <filename>',
121
+ ' OR: tusky rehydrate <walrusBlobId> -o <filename>',
122
+ '',
123
+ '--- DECRYPT PRIVATE VAULT FILES ---',
124
+ 'Files with encrypted=true are AES-256-GCM encrypted.',
125
+ '',
126
+ 'Option A — Using the Tusky CLI decrypt command (easiest):',
127
+ ' tusky decrypt <encrypted-file> --export <this-file.json> -o <output>',
128
+ ' This reads the wrappedKey/iv from this manifest and prompts for your passphrase.',
129
+ ' You can also pass --passphrase <passphrase> or set TUSKYDP_PASSWORD env var.',
130
+ ' If the Tusky API is still reachable, you can skip the export and use:',
131
+ ' tusky decrypt <encrypted-file> --file-id <fileId> -o <output>',
132
+ '',
133
+ 'Option B — Using the standalone script (zero dependencies, fully offline):',
134
+ ' node tusky-decrypt.mjs <encrypted-file> <output> <passphrase> <salt> <wrappedKey> <iv> [encryptedMasterKey]',
135
+ ' The salt and encryptedMasterKey come from your account encryption params.',
136
+ '',
137
+ 'Option C — Manual decryption (any language with AES-256-GCM + PBKDF2):',
138
+ ' 1. Derive wrapping key: PBKDF2(passphrase, salt, 600000 iterations, SHA-256, 32 bytes)',
139
+ ' 2. If your account has an encryptedMasterKey, unwrap it: AES-256-GCM-decrypt(encryptedMasterKey, wrappingKey)',
140
+ ' Otherwise the wrapping key IS the master key (legacy accounts)',
141
+ ' 3. Unwrap the file key: AES-256-GCM-decrypt(wrappedKey, masterKey)',
142
+ ' wrappedKey format: [12-byte IV | ciphertext | 16-byte auth tag]',
143
+ ' 4. Decrypt the file: AES-256-GCM-decrypt(fileData, fileKey, encryptionIv)',
144
+ ' File data format: [ciphertext | 16-byte auth tag]',
145
+ ' 5. Verify integrity: SHA-256(plaintext) should match plaintextChecksumSha256',
146
+ '',
147
+ '--- RENEW WALRUS EPOCHS ---',
148
+ 'Files with a walrusBlobObjectId can have their storage extended:',
149
+ ' walrus extend <walrusBlobObjectId> --epochs 5',
150
+ 'This requires a Sui wallet with WAL tokens. Check walrusEpochEnd to see when renewal is needed.',
151
+ ],
152
+ },
153
+ };
154
+ // Write to disk
155
+ const outputPath = resolve(options.output);
156
+ writeFileSync(outputPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
157
+ spinner.succeed(`Exported ${exportedFiles.length} files from ${vaults.length} vault(s)`);
158
+ console.log(chalk.dim(` Output: ${outputPath}`));
159
+ console.log('');
160
+ // Summary stats
161
+ const withBlob = exportedFiles.filter((f) => f.walrusBlobId).length;
162
+ const encrypted = exportedFiles.filter((f) => f.encrypted).length;
163
+ const hotOnly = exportedFiles.filter((f) => !f.walrusBlobId && f.status === 'hot').length;
164
+ console.log(` On Walrus: ${withBlob} files (recoverable from Walrus network)`);
165
+ console.log(` Encrypted: ${encrypted} files (require passphrase to decrypt)`);
166
+ if (hotOnly > 0) {
167
+ console.log(chalk.yellow(` Hot only: ${hotOnly} files (NOT yet on Walrus — only in Tusky hot cache)`));
168
+ }
169
+ console.log('');
170
+ console.log(chalk.yellow(' Store this file securely — it contains encryption keys.'));
171
+ console.log(chalk.dim(' See the "recovery.instructions" field inside for full recovery steps.'));
172
+ }
173
+ catch (err) {
174
+ spinner.fail(`Export failed: ${err.message}`);
175
+ }
176
+ });
177
+ }
178
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/commands/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA2DnD,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,+EAA+E,CAAC;SAC5F,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,EAAE,mBAAmB,CAAC;SACtE,MAAM,CAAC,mBAAmB,EAAE,yCAAyC,CAAC;SACtE,MAAM,CAAC,KAAK,EAAE,OAA2C,EAAE,EAAE;QAC5D,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,0BAA0B,CAAC,CAAC;QAC1D,OAAO,CAAC,KAAK,EAAE,CAAC;QAEhB,IAAI,CAAC;YACH,oCAAoC;YACpC,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YACxC,MAAM,gBAAgB,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;YAEjE,mBAAmB;YACnB,OAAO,CAAC,IAAI,GAAG,oBAAoB,CAAC;YACpC,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK;gBAC1B,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC;gBACjD,CAAC,CAAC,SAAS,CAAC;YAEd,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;gBACvF,OAAO;YACT,CAAC;YAED,qBAAqB;YACrB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAE1D,kCAAkC;YAClC,OAAO,CAAC,IAAI,GAAG,mBAAmB,CAAC;YACnC,MAAM,aAAa,GAAmB,EAAE,CAAC;YAEzC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,MAA0B,CAAC;gBAC/B,GAAG,CAAC;oBACF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;wBAClC,OAAO,EAAE,KAAK,CAAC,EAAE;wBACjB,KAAK,EAAE,GAAG;wBACV,MAAM;qBACP,CAAC,CAAC;oBAEH,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;wBAChC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;wBACrC,aAAa,CAAC,IAAI,CAAC;4BACjB,MAAM,EAAE,IAAI,CAAC,EAAE;4BACf,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;4BACvB,SAAS,EAAE,IAAI,CAAC,SAAS;4BACzB,OAAO,EAAE,IAAI,CAAC,OAAO;4BACrB,SAAS,EAAE,CAAC,EAAE,IAAI,IAAI,SAAS;4BAC/B,eAAe,EAAE,CAAC,EAAE,UAAU,IAAI,SAAS;4BAC3C,QAAQ,EAAE,IAAI,CAAC,QAAQ;4BACvB,MAAM,EAAE,IAAI,CAAC,MAAM;4BACnB,YAAY,EAAE,IAAI,CAAC,YAAY;4BAC/B,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;4BAC3C,cAAc,EAAE,IAAI,CAAC,cAAc;4BACnC,SAAS,EAAE,IAAI,CAAC,SAAS;4BACzB,UAAU,EAAE,IAAI,CAAC,UAAU;4BAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;4BAC/B,uBAAuB,EAAE,IAAI,CAAC,uBAAuB;4BACrD,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;4BAC3C,SAAS,EAAE,IAAI,CAAC,SAAS;yBAC1B,CAAC,CAAC;oBACL,CAAC;oBAED,MAAM,GAAG,MAAM,CAAC,UAAU,IAAI,SAAS,CAAC;oBACxC,OAAO,CAAC,IAAI,GAAG,sBAAsB,aAAa,CAAC,MAAM,UAAU,CAAC;gBACtE,CAAC,QAAQ,MAAM,EAAE;YACnB,CAAC;YAED,gCAAgC;YAChC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,YAAY,CAAC;YACvD,MAAM,SAAS,GAAG,QAAQ,KAAK,YAAY,CAAC;YAC5C,MAAM,mBAAmB,GAAG,SAAS;gBACnC,CAAC,CAAC,iCAAiC;gBACnC,CAAC,CAAC,gDAAgD,CAAC;YAErD,iBAAiB;YACjB,MAAM,QAAQ,GAAmB;gBAC/B,OAAO,EAAE,CAAC;gBACV,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACpC,OAAO,EAAE;oBACP,EAAE,EAAE,OAAO,CAAC,EAAE;oBACd,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,IAAI,EAAE,OAAO,CAAC,QAAQ;iBACvB;gBACD,UAAU,EAAE;oBACV,aAAa,EAAE,gBAAgB,CAAC,aAAa;oBAC7C,IAAI,EAAE,gBAAgB,CAAC,IAAI,IAAI,IAAI;oBACnC,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,IAAI,IAAI;oBAC3C,kBAAkB,EAAE,gBAAgB,CAAC,kBAAkB,IAAI,IAAI;iBAChE;gBACD,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;gBAChD,mBAAmB;gBACnB,UAAU,EAAE,aAAa,CAAC,MAAM;gBAChC,WAAW,EAAE,MAAM,CAAC,MAAM;gBAC1B,KAAK,EAAE,aAAa;gBACpB,QAAQ,EAAE;oBACR,YAAY,EAAE;wBACZ,iCAAiC;wBACjC,EAAE;wBACF,mFAAmF;wBACnF,EAAE;wBACF,oCAAoC;wBACpC,+EAA+E;wBAC/E,wBAAwB,mBAAmB,0BAA0B;wBACrE,gDAAgD;wBAChD,oDAAoD;wBACpD,EAAE;wBACF,qCAAqC;wBACrC,sDAAsD;wBACtD,EAAE;wBACF,2DAA2D;wBAC3D,wEAAwE;wBACxE,oFAAoF;wBACpF,gFAAgF;wBAChF,yEAAyE;wBACzE,iEAAiE;wBACjE,EAAE;wBACF,4EAA4E;wBAC5E,+GAA+G;wBAC/G,6EAA6E;wBAC7E,EAAE;wBACF,wEAAwE;wBACxE,0FAA0F;wBAC1F,iHAAiH;wBACjH,qEAAqE;wBACrE,sEAAsE;wBACtE,sEAAsE;wBACtE,6EAA6E;wBAC7E,wDAAwD;wBACxD,gFAAgF;wBAChF,EAAE;wBACF,6BAA6B;wBAC7B,kEAAkE;wBAClE,iDAAiD;wBACjD,iGAAiG;qBAClG;iBACF;aACF,CAAC;YAEF,gBAAgB;YAChB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC3C,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;YAE7E,OAAO,CAAC,OAAO,CAAC,YAAY,aAAa,CAAC,MAAM,eAAe,MAAM,CAAC,MAAM,WAAW,CAAC,CAAC;YACzF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,UAAU,EAAE,CAAC,CAAC,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,gBAAgB;YAChB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC;YACpE,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;YAClE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,MAAM,CAAC;YAE1F,OAAO,CAAC,GAAG,CAAC,oBAAoB,QAAQ,0CAA0C,CAAC,CAAC;YACpF,OAAO,CAAC,GAAG,CAAC,oBAAoB,SAAS,wCAAwC,CAAC,CAAC;YACnF,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,OAAO,sDAAsD,CAAC,CAAC,CAAC;YAC/G,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,2DAA2D,CAAC,CAAC,CAAC;YACvF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC,CAAC;QACpG,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,kBAAkB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerFileCommands(program: Command): void;
3
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/commands/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAoLpD"}
@@ -0,0 +1,179 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import { cliConfig } from '../config.js';
4
+ import { getSDKClient } from '../sdk.js';
5
+ import { createTable, formatBytes, formatDate } from '../lib/output.js';
6
+ import { resolveVault } from '../lib/resolve.js';
7
+ export function registerFileCommands(program) {
8
+ // ls command (top-level shortcut)
9
+ program.command('ls [vault]')
10
+ .description('List files')
11
+ .option('--sort <field>', 'Sort by: name, size, date', 'date')
12
+ .option('--status <status>', 'Filter by status')
13
+ .option('--limit <n>', 'Max results', '50')
14
+ .option('-f, --follow', 'Watch mode: refresh every 3 seconds')
15
+ .action(async (vaultRef, options) => {
16
+ const sdk = getSDKClient(program);
17
+ const format = program.opts().format || cliConfig.get('outputFormat');
18
+ let vaultId;
19
+ if (vaultRef) {
20
+ vaultId = await resolveVault(sdk, vaultRef);
21
+ }
22
+ const sortMap = { name: 'name', size: 'sizeBytes', date: 'createdAt' };
23
+ const fetchAndRender = async () => {
24
+ const { files } = await sdk.files.list({
25
+ vaultId,
26
+ limit: parseInt(options.limit),
27
+ sortBy: sortMap[options.sort],
28
+ order: 'desc',
29
+ });
30
+ if (format === 'json') {
31
+ console.log(JSON.stringify({ files }, null, 2));
32
+ return;
33
+ }
34
+ if (files.length === 0) {
35
+ console.log(chalk.dim('No files found.'));
36
+ return;
37
+ }
38
+ const table = createTable(['Name', 'Size', 'Status', 'Uploaded', 'ID']);
39
+ for (const f of files) {
40
+ const statusColors = {
41
+ hot: chalk.green,
42
+ synced: chalk.blue,
43
+ cold: chalk.yellow,
44
+ error: chalk.red,
45
+ uploading: chalk.dim,
46
+ };
47
+ const statusColor = statusColors[f.status] || chalk.white;
48
+ table.push([
49
+ f.name,
50
+ formatBytes(f.plaintextSizeBytes || f.sizeBytes),
51
+ statusColor(f.status),
52
+ formatDate(f.createdAt),
53
+ chalk.dim(f.id),
54
+ ]);
55
+ }
56
+ console.log(table.toString());
57
+ };
58
+ if (options.follow) {
59
+ const clear = () => process.stdout.write('\x1B[2J\x1B[H');
60
+ const render = async () => {
61
+ clear();
62
+ console.log(chalk.dim(`Watching files — refreshing every 3s (Ctrl+C to stop)\n`));
63
+ try {
64
+ await fetchAndRender();
65
+ }
66
+ catch (err) {
67
+ console.error(chalk.red(`Error: ${err.message}`));
68
+ }
69
+ };
70
+ await render();
71
+ const interval = setInterval(render, 3000);
72
+ process.on('SIGINT', () => {
73
+ clearInterval(interval);
74
+ process.exit(0);
75
+ });
76
+ // Keep process alive
77
+ await new Promise(() => { });
78
+ }
79
+ else {
80
+ await fetchAndRender();
81
+ }
82
+ });
83
+ // info command
84
+ program.command('info <file-id>')
85
+ .description('Show file details')
86
+ .action(async (fileId) => {
87
+ const sdk = getSDKClient(program);
88
+ const format = program.opts().format || cliConfig.get('outputFormat');
89
+ const file = await sdk.files.getStatus(fileId);
90
+ if (format === 'json') {
91
+ console.log(JSON.stringify(file, null, 2));
92
+ return;
93
+ }
94
+ console.log(`Name: ${file.name}`);
95
+ console.log(`Size: ${formatBytes(file.sizeBytes)}`);
96
+ console.log(`MIME: ${file.mimeType}`);
97
+ console.log(`Status: ${file.status}`);
98
+ if (file.walrusBlobId)
99
+ console.log(`Walrus Blob: ${file.walrusBlobId}`);
100
+ console.log(`Uploaded: ${new Date(file.createdAt).toISOString()}`);
101
+ console.log(`ID: ${file.fileId}`);
102
+ });
103
+ // status command
104
+ program.command('status <file-id>')
105
+ .description('Show sync status')
106
+ .action(async (fileId) => {
107
+ const sdk = getSDKClient(program);
108
+ const file = await sdk.files.getStatus(fileId);
109
+ console.log(`File: ${file.name}`);
110
+ console.log(`Status: ${file.status}`);
111
+ if (file.walrusBlobId) {
112
+ console.log(`Walrus Blob: ${file.walrusBlobId}`);
113
+ }
114
+ else {
115
+ console.log(`Walrus: ${chalk.dim('Not synced yet')}`);
116
+ }
117
+ });
118
+ // retry command
119
+ program.command('retry [file-ids...]')
120
+ .description('Retry failed Walrus sync (all errored files if no IDs given)')
121
+ .action(async (fileIds) => {
122
+ const sdk = getSDKClient(program);
123
+ const format = program.opts().format || cliConfig.get('outputFormat');
124
+ if (fileIds.length === 0) {
125
+ const result = await sdk.files.retryAll();
126
+ if (format === 'json') {
127
+ console.log(JSON.stringify(result, null, 2));
128
+ }
129
+ else if (result.retriedCount === 0) {
130
+ console.log(chalk.dim('No files in error status.'));
131
+ }
132
+ else {
133
+ console.log(chalk.green(`Re-queued ${result.retriedCount} file(s) for Walrus sync.`));
134
+ }
135
+ return;
136
+ }
137
+ for (const fileId of fileIds) {
138
+ try {
139
+ const result = await sdk.files.retry(fileId);
140
+ if (format === 'json') {
141
+ console.log(JSON.stringify(result, null, 2));
142
+ }
143
+ else {
144
+ console.log(chalk.green(`Retried: ${fileId}`));
145
+ }
146
+ }
147
+ catch (err) {
148
+ console.error(chalk.red(`Failed to retry ${fileId}: ${err.message}`));
149
+ }
150
+ }
151
+ });
152
+ // rm command
153
+ program.command('rm <file-ids...>')
154
+ .description('Delete one or more files')
155
+ .option('--force', 'Skip confirmation prompt')
156
+ .action(async (fileIds, options) => {
157
+ const sdk = getSDKClient(program);
158
+ if (!options.force) {
159
+ const answers = await inquirer.prompt([{
160
+ type: 'confirm',
161
+ name: 'confirm',
162
+ message: `Delete ${fileIds.length} file(s)? This cannot be undone.`,
163
+ default: false,
164
+ }]);
165
+ if (!answers.confirm)
166
+ return;
167
+ }
168
+ for (const fileId of fileIds) {
169
+ try {
170
+ await sdk.files.delete(fileId);
171
+ console.log(chalk.green(`Deleted: ${fileId}`));
172
+ }
173
+ catch (err) {
174
+ console.error(chalk.red(`Failed to delete ${fileId}: ${err.message}`));
175
+ }
176
+ }
177
+ });
178
+ }
179
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/commands/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Commander registration for `tusky mcp` subcommands:
3
+ * - tusky mcp serve — Start the MCP server (stdio transport)
4
+ * - tusky mcp install-config — Write MCP config for Claude Code / Cursor
5
+ */
6
+ import type { Command } from 'commander';
7
+ export declare function registerMcpCommands(program: Command): void;
8
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../src/commands/mcp.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiIzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QAoFnD"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Commander registration for `tusky mcp` subcommands:
3
+ * - tusky mcp serve — Start the MCP server (stdio transport)
4
+ * - tusky mcp install-config — Write MCP config for Claude Code / Cursor
5
+ */
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
7
+ import { join, dirname, resolve } from 'path';
8
+ import { homedir } from 'os';
9
+ import chalk from 'chalk';
10
+ import { getApiUrl, getApiKey, cliConfig } from '../config.js';
11
+ import { startMcpServer } from '../mcp/server.js';
12
+ function getTargets() {
13
+ const home = homedir();
14
+ return [
15
+ {
16
+ name: 'claude-code',
17
+ configPath: join(process.cwd(), '.mcp.json'),
18
+ description: 'Claude Code (project-level .mcp.json in current directory)',
19
+ },
20
+ {
21
+ name: 'claude-desktop',
22
+ configPath: process.platform === 'win32'
23
+ ? join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json')
24
+ : join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
25
+ description: 'Claude Desktop',
26
+ },
27
+ {
28
+ name: 'cursor',
29
+ configPath: join(home, '.cursor', 'mcp.json'),
30
+ description: 'Cursor editor',
31
+ },
32
+ ];
33
+ }
34
+ // ---------------------------------------------------------------------------
35
+ // Helpers
36
+ // ---------------------------------------------------------------------------
37
+ function getTuskyBinaryCommand() {
38
+ // Strategy: use absolute paths to avoid PATH resolution failures.
39
+ // Agent hosts (Claude Code, Cursor, Desktop) spawn processes with a
40
+ // minimal PATH that won't include nvm shims, pnpm globals, etc.
41
+ //
42
+ // Priority:
43
+ // 1. If `tusky` exists in a well-known PATH dir → use it directly
44
+ // (standard npm -g install lands in /usr/local/bin or /opt/homebrew/bin)
45
+ // 2. Resolve absolute path to node + our entry script
46
+ // (works for pnpm link, local dev, non-standard installs)
47
+ // 3. Fallback to npx
48
+ // Check if `tusky` is available in the standard agent-visible PATH dirs
49
+ const agentPathDirs = ['/usr/local/bin', '/opt/homebrew/bin', '/usr/bin'];
50
+ for (const dir of agentPathDirs) {
51
+ const tuskyBin = join(dir, 'tusky');
52
+ if (existsSync(tuskyBin)) {
53
+ return { command: tuskyBin, args: ['mcp', 'serve'] };
54
+ }
55
+ }
56
+ // Not in standard PATH — resolve absolute node + entry script paths
57
+ // (handles pnpm link --global, local dev, monorepo installs)
58
+ const nodeExe = process.execPath;
59
+ const thisDir = dirname(new URL(import.meta.url).pathname);
60
+ // Try: <thisDir>/../../bin/tuskydp.js (running from dist/commands/)
61
+ const entryPoint = resolve(join(thisDir, '..', '..', 'bin', 'tuskydp.js'));
62
+ if (existsSync(entryPoint)) {
63
+ return { command: nodeExe, args: [entryPoint, 'mcp', 'serve'] };
64
+ }
65
+ // Try: <thisDir>/../../../dist/bin/tuskydp.js (running from src/commands/ in dev)
66
+ const distEntry = resolve(join(thisDir, '..', '..', '..', 'dist', 'bin', 'tuskydp.js'));
67
+ if (existsSync(distEntry)) {
68
+ return { command: nodeExe, args: [distEntry, 'mcp', 'serve'] };
69
+ }
70
+ // Last resort: npx (works if npm/npx is in the agent's PATH)
71
+ return { command: 'npx', args: ['-y', '@tuskydp/cli', 'mcp', 'serve'] };
72
+ }
73
+ function buildMcpServerEntry(apiKey, apiUrl) {
74
+ const { command, args } = getTuskyBinaryCommand();
75
+ const env = {};
76
+ if (apiKey)
77
+ env.TUSKYDP_API_KEY = apiKey;
78
+ if (apiUrl)
79
+ env.TUSKYDP_API_URL = apiUrl;
80
+ // Placeholder for password — user fills in
81
+ env.TUSKYDP_PASSWORD = '';
82
+ const entry = {
83
+ command,
84
+ args,
85
+ };
86
+ if (Object.keys(env).length > 0) {
87
+ entry.env = env;
88
+ }
89
+ return entry;
90
+ }
91
+ function readJsonFile(filePath) {
92
+ if (!existsSync(filePath))
93
+ return {};
94
+ try {
95
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
96
+ }
97
+ catch {
98
+ return {};
99
+ }
100
+ }
101
+ function writeJsonFile(filePath, data) {
102
+ mkdirSync(dirname(filePath), { recursive: true });
103
+ writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Command registration
107
+ // ---------------------------------------------------------------------------
108
+ export function registerMcpCommands(program) {
109
+ const mcp = program
110
+ .command('mcp')
111
+ .description('MCP server for AI agents (Claude Code, Cursor, etc.)');
112
+ // ── tusky mcp serve ──────────────────────────────────────────────────
113
+ mcp
114
+ .command('serve')
115
+ .description('Start the MCP server (stdio transport for AI agent integration)')
116
+ .action(async () => {
117
+ // Resolve API credentials from the same priority chain as all CLI commands
118
+ const root = mcp.parent || mcp;
119
+ const apiUrl = getApiUrl(root.opts().apiUrl);
120
+ const apiKey = getApiKey(root.opts().apiKey);
121
+ await startMcpServer({ apiKey, apiUrl });
122
+ });
123
+ // ── tusky mcp install-config ─────────────────────────────────────────
124
+ mcp
125
+ .command('install-config')
126
+ .description('Add Tusky MCP server config to Claude Code, Claude Desktop, or Cursor')
127
+ .option('--target <target>', 'Target client: claude-code, claude-desktop, cursor', 'claude-code')
128
+ .option('--api-key <key>', 'API key to embed (defaults to stored key)')
129
+ .option('--api-url <url>', 'API URL override')
130
+ .action(async (options) => {
131
+ const targets = getTargets();
132
+ const target = targets.find((t) => t.name === options.target);
133
+ if (!target) {
134
+ console.error(chalk.red(`Unknown target "${options.target}". Valid targets: ${targets.map((t) => t.name).join(', ')}`));
135
+ process.exit(1);
136
+ }
137
+ // Resolve API key (flag > stored > error)
138
+ const root = mcp.parent || mcp;
139
+ const apiKey = options.apiKey ?? root.opts().apiKey ?? process.env.TUSKYDP_API_KEY ?? cliConfig.get('apiKey');
140
+ const apiUrl = options.apiUrl ?? root.opts().apiUrl ?? process.env.TUSKYDP_API_URL;
141
+ // Only include non-default values
142
+ const resolvedApiUrl = apiUrl && apiUrl !== 'https://api.tusky.dev' ? apiUrl : undefined;
143
+ // Build the server entry
144
+ const serverEntry = buildMcpServerEntry(apiKey, resolvedApiUrl);
145
+ // Read existing config
146
+ const config = readJsonFile(target.configPath);
147
+ // Merge into mcpServers
148
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
149
+ config.mcpServers = {};
150
+ }
151
+ const mcpServers = config.mcpServers;
152
+ const existed = 'tusky' in mcpServers;
153
+ mcpServers.tusky = serverEntry;
154
+ // Write back
155
+ writeJsonFile(target.configPath, config);
156
+ console.log(chalk.green(`${existed ? 'Updated' : 'Added'} Tusky MCP server config for ${target.description}`));
157
+ console.log(chalk.dim(` Config file: ${target.configPath}`));
158
+ console.log('');
159
+ if (!apiKey) {
160
+ console.log(chalk.yellow(' Note: No API key found. Add your API key to the config:'));
161
+ console.log(chalk.dim(` Set TUSKYDP_API_KEY in the env block of ${target.configPath}`));
162
+ console.log('');
163
+ }
164
+ console.log(chalk.yellow(' Remember to set TUSKYDP_PASSWORD in the config for private vault encryption:'));
165
+ console.log(chalk.dim(` Edit the env block in ${target.configPath}`));
166
+ console.log('');
167
+ if (options.target === 'claude-desktop') {
168
+ console.log(chalk.dim(' Restart Claude Desktop to pick up the changes.'));
169
+ }
170
+ else if (options.target === 'claude-code') {
171
+ console.log(chalk.dim(' Claude Code will automatically detect the .mcp.json file.'));
172
+ }
173
+ else if (options.target === 'cursor') {
174
+ console.log(chalk.dim(' Restart Cursor to pick up the changes.'));
175
+ }
176
+ });
177
+ }
178
+ //# sourceMappingURL=mcp.js.map