@tuskydp/cli 0.2.1 → 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.
- package/CHANGELOG.md +39 -0
- package/dist/src/commands/account.d.ts.map +1 -1
- package/dist/src/commands/account.js +0 -1
- package/dist/src/commands/account.js.map +1 -1
- package/dist/src/commands/auth.d.ts.map +1 -1
- package/dist/src/commands/auth.js +8 -5
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/download.d.ts +1 -0
- package/dist/src/commands/download.d.ts.map +1 -1
- package/dist/src/commands/download.js +35 -22
- package/dist/src/commands/download.js.map +1 -1
- package/dist/src/commands/export.d.ts +9 -24
- package/dist/src/commands/export.d.ts.map +1 -1
- package/dist/src/commands/export.js +31 -59
- package/dist/src/commands/export.js.map +1 -1
- package/dist/src/commands/files.d.ts.map +1 -1
- package/dist/src/commands/files.js +91 -12
- package/dist/src/commands/files.js.map +1 -1
- package/dist/src/commands/folder.d.ts +3 -0
- package/dist/src/commands/folder.d.ts.map +1 -0
- package/dist/src/commands/folder.js +151 -0
- package/dist/src/commands/folder.js.map +1 -0
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +15 -9
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/rehydrate.d.ts +1 -0
- package/dist/src/commands/rehydrate.d.ts.map +1 -1
- package/dist/src/commands/rehydrate.js +15 -7
- package/dist/src/commands/rehydrate.js.map +1 -1
- package/dist/src/commands/sui.d.ts +3 -0
- package/dist/src/commands/sui.d.ts.map +1 -0
- package/dist/src/commands/sui.js +64 -0
- package/dist/src/commands/sui.js.map +1 -0
- package/dist/src/commands/trash.d.ts +3 -0
- package/dist/src/commands/trash.d.ts.map +1 -0
- package/dist/src/commands/trash.js +109 -0
- package/dist/src/commands/trash.js.map +1 -0
- package/dist/src/commands/upload.d.ts +4 -0
- package/dist/src/commands/upload.d.ts.map +1 -1
- package/dist/src/commands/upload.js +82 -27
- package/dist/src/commands/upload.js.map +1 -1
- package/dist/src/commands/vault.d.ts.map +1 -1
- package/dist/src/commands/vault.js +2 -24
- package/dist/src/commands/vault.js.map +1 -1
- package/dist/src/commands/wallet.d.ts +3 -0
- package/dist/src/commands/wallet.d.ts.map +1 -0
- package/dist/src/commands/wallet.js +126 -0
- package/dist/src/commands/wallet.js.map +1 -0
- package/dist/src/commands/webhook.d.ts +3 -0
- package/dist/src/commands/webhook.d.ts.map +1 -0
- package/dist/src/commands/webhook.js +172 -0
- package/dist/src/commands/webhook.js.map +1 -0
- package/dist/src/config.d.ts +2 -2
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +2 -3
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.js +19 -9
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/resolve.d.ts.map +1 -1
- package/dist/src/lib/resolve.js +4 -5
- package/dist/src/lib/resolve.js.map +1 -1
- package/dist/src/mcp/context.d.ts +1 -9
- package/dist/src/mcp/context.d.ts.map +1 -1
- package/dist/src/mcp/context.js +1 -2
- package/dist/src/mcp/context.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +2 -59
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/account.d.ts.map +1 -1
- package/dist/src/mcp/tools/account.js +1 -3
- package/dist/src/mcp/tools/account.js.map +1 -1
- package/dist/src/mcp/tools/files.d.ts +2 -3
- package/dist/src/mcp/tools/files.d.ts.map +1 -1
- package/dist/src/mcp/tools/files.js +46 -49
- package/dist/src/mcp/tools/files.js.map +1 -1
- package/dist/src/mcp/tools/vaults.js +2 -2
- package/dist/src/mcp/tools/vaults.js.map +1 -1
- package/dist/src/seal.d.ts +16 -0
- package/dist/src/seal.d.ts.map +1 -1
- package/dist/src/seal.js +23 -0
- package/dist/src/seal.js.map +1 -1
- package/dist/src/tui/files-panel.d.ts +31 -2
- package/dist/src/tui/files-panel.d.ts.map +1 -1
- package/dist/src/tui/files-panel.js +119 -13
- package/dist/src/tui/files-panel.js.map +1 -1
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +252 -48
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/overview.d.ts.map +1 -1
- package/dist/src/tui/overview.js +21 -9
- package/dist/src/tui/overview.js.map +1 -1
- package/dist/src/tui/trash-screen.d.ts +4 -0
- package/dist/src/tui/trash-screen.d.ts.map +1 -0
- package/dist/src/tui/trash-screen.js +190 -0
- package/dist/src/tui/trash-screen.js.map +1 -0
- package/dist/src/tui/vaults-panel.d.ts +8 -0
- package/dist/src/tui/vaults-panel.d.ts.map +1 -1
- package/dist/src/tui/vaults-panel.js +45 -6
- package/dist/src/tui/vaults-panel.js.map +1 -1
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.d.ts.map +1 -0
- package/dist/src/version.js +21 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +3 -3
- package/src/__tests__/seal.test.ts +7 -54
- package/src/commands/account.ts +0 -1
- package/src/commands/auth.ts +7 -5
- package/src/commands/download.ts +38 -28
- package/src/commands/export.ts +37 -81
- package/src/commands/files.ts +95 -11
- package/src/commands/folder.ts +169 -0
- package/src/commands/mcp.ts +16 -10
- package/src/commands/rehydrate.ts +15 -8
- package/src/commands/sui.ts +69 -0
- package/src/commands/trash.ts +121 -0
- package/src/commands/upload.ts +98 -31
- package/src/commands/vault.ts +2 -23
- package/src/commands/wallet.ts +183 -0
- package/src/commands/webhook.ts +193 -0
- package/src/config.ts +3 -4
- package/src/index.ts +19 -10
- package/src/lib/resolve.ts +3 -4
- package/src/mcp/context.ts +1 -11
- package/src/mcp/server.ts +2 -70
- package/src/mcp/tools/account.ts +1 -3
- package/src/mcp/tools/files.ts +50 -63
- package/src/mcp/tools/vaults.ts +3 -3
- package/src/seal.ts +34 -1
- package/src/tui/files-panel.ts +140 -14
- package/src/tui/index.ts +264 -52
- package/src/tui/overview.ts +20 -9
- package/src/tui/trash-screen.ts +203 -0
- package/src/tui/vaults-panel.ts +55 -6
- package/src/version.ts +21 -0
- package/vitest.config.ts +1 -0
- package/dist/src/client.d.ts +0 -120
- package/dist/src/client.d.ts.map +0 -1
- package/dist/src/client.js +0 -152
- package/dist/src/client.js.map +0 -1
- package/dist/src/commands/decrypt.d.ts +0 -15
- package/dist/src/commands/decrypt.d.ts.map +0 -1
- package/dist/src/commands/decrypt.js +0 -224
- package/dist/src/commands/decrypt.js.map +0 -1
- package/dist/src/commands/encryption.d.ts +0 -3
- package/dist/src/commands/encryption.d.ts.map +0 -1
- package/dist/src/commands/encryption.js +0 -254
- package/dist/src/commands/encryption.js.map +0 -1
- package/dist/src/crypto.d.ts +0 -16
- package/dist/src/crypto.d.ts.map +0 -1
- package/dist/src/crypto.js +0 -95
- package/dist/src/crypto.js.map +0 -1
- package/dist/src/lib/keyring.d.ts +0 -4
- package/dist/src/lib/keyring.d.ts.map +0 -1
- package/dist/src/lib/keyring.js +0 -49
- package/dist/src/lib/keyring.js.map +0 -1
- package/src/__tests__/crypto.test.ts +0 -315
- package/src/commands/decrypt.ts +0 -276
- package/src/commands/encryption.ts +0 -305
- package/src/crypto.ts +0 -130
- package/src/lib/keyring.ts +0 -50
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
|
|
4
|
+
import { cliConfig, getApiUrl, getApiKey } from '../config.js';
|
|
5
|
+
import { createTable, formatDate } from '../lib/output.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Lightweight fetch helper that uses the API key for auth.
|
|
9
|
+
* The SDK does not have a wallet resource, so we call the API directly.
|
|
10
|
+
*/
|
|
11
|
+
async function walletFetch<T>(apiUrl: string, apiKey: string, path: string, init?: RequestInit): Promise<T> {
|
|
12
|
+
const url = `${apiUrl.replace(/\/$/, '')}${path}`;
|
|
13
|
+
const response = await fetch(url, {
|
|
14
|
+
...init,
|
|
15
|
+
headers: {
|
|
16
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
...(init?.headers || {}),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
const body = await response.json().catch(() => ({ error: response.statusText }));
|
|
23
|
+
throw new Error((body as Record<string, string>).error || `HTTP ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json() as Promise<T>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface WalletInfo {
|
|
29
|
+
id: string;
|
|
30
|
+
suiAddress: string;
|
|
31
|
+
walBalance: string;
|
|
32
|
+
walBalanceFormatted: string;
|
|
33
|
+
suiBalance: string;
|
|
34
|
+
suiBalanceFormatted: string;
|
|
35
|
+
usdcBalance?: string;
|
|
36
|
+
usdcBalanceFormatted?: string;
|
|
37
|
+
createdAt: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface DepositInfo {
|
|
41
|
+
suiAddress: string;
|
|
42
|
+
network: string;
|
|
43
|
+
supportedTokens: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PaymentEntry {
|
|
47
|
+
id: string;
|
|
48
|
+
fileId: string;
|
|
49
|
+
type: string;
|
|
50
|
+
walMist: string;
|
|
51
|
+
suiGasMist: string;
|
|
52
|
+
epochs: number;
|
|
53
|
+
createdAt: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface PaymentsResponse {
|
|
57
|
+
payments: PaymentEntry[];
|
|
58
|
+
totalSpentWal: string;
|
|
59
|
+
totalSpentWalFormatted: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function registerWalletCommands(program: Command) {
|
|
63
|
+
const wallet = program.command('wallet').description('Manage wallet and payments (PPU)');
|
|
64
|
+
|
|
65
|
+
// ── info ────────────────────────────────────────────────────────────
|
|
66
|
+
wallet.command('info')
|
|
67
|
+
.description('Show wallet balances and address')
|
|
68
|
+
.action(async () => {
|
|
69
|
+
const root = wallet.parent || wallet;
|
|
70
|
+
const apiUrl = getApiUrl(root.opts().apiUrl);
|
|
71
|
+
const apiKey = getApiKey(root.opts().apiKey);
|
|
72
|
+
const format = root.opts().format || cliConfig.get('outputFormat');
|
|
73
|
+
|
|
74
|
+
const w = await walletFetch<WalletInfo>(apiUrl, apiKey, '/api/wallet');
|
|
75
|
+
|
|
76
|
+
if (format === 'json') {
|
|
77
|
+
console.log(JSON.stringify(w, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(chalk.bold('Wallet'));
|
|
82
|
+
console.log(` Address: ${w.suiAddress}`);
|
|
83
|
+
console.log(` WAL: ${w.walBalanceFormatted}`);
|
|
84
|
+
console.log(` SUI: ${w.suiBalanceFormatted}`);
|
|
85
|
+
if (w.usdcBalanceFormatted) {
|
|
86
|
+
console.log(` USDC: ${w.usdcBalanceFormatted}`);
|
|
87
|
+
}
|
|
88
|
+
console.log(` Created: ${new Date(w.createdAt).toLocaleString()}`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ── deposit ─────────────────────────────────────────────────────────
|
|
92
|
+
wallet.command('deposit')
|
|
93
|
+
.description('Show deposit address and supported tokens')
|
|
94
|
+
.action(async () => {
|
|
95
|
+
const root = wallet.parent || wallet;
|
|
96
|
+
const apiUrl = getApiUrl(root.opts().apiUrl);
|
|
97
|
+
const apiKey = getApiKey(root.opts().apiKey);
|
|
98
|
+
const format = root.opts().format || cliConfig.get('outputFormat');
|
|
99
|
+
|
|
100
|
+
const d = await walletFetch<DepositInfo>(apiUrl, apiKey, '/api/wallet/deposit-info');
|
|
101
|
+
|
|
102
|
+
if (format === 'json') {
|
|
103
|
+
console.log(JSON.stringify(d, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log(chalk.bold('Deposit Info'));
|
|
108
|
+
console.log(` Address: ${d.suiAddress}`);
|
|
109
|
+
console.log(` Network: ${d.network}`);
|
|
110
|
+
console.log(` Tokens: ${d.supportedTokens.join(', ')}`);
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(chalk.dim('Send WAL, SUI, or USDC to the address above on the Sui network.'));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ── payments ────────────────────────────────────────────────────────
|
|
116
|
+
wallet.command('payments')
|
|
117
|
+
.description('List payment history')
|
|
118
|
+
.option('--limit <n>', 'Max results', '20')
|
|
119
|
+
.action(async (options) => {
|
|
120
|
+
const root = wallet.parent || wallet;
|
|
121
|
+
const apiUrl = getApiUrl(root.opts().apiUrl);
|
|
122
|
+
const apiKey = getApiKey(root.opts().apiKey);
|
|
123
|
+
const format = root.opts().format || cliConfig.get('outputFormat');
|
|
124
|
+
|
|
125
|
+
const data = await walletFetch<PaymentsResponse>(
|
|
126
|
+
apiUrl, apiKey,
|
|
127
|
+
`/api/wallet/payments?limit=${options.limit}`,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (format === 'json') {
|
|
131
|
+
console.log(JSON.stringify(data, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (data.payments.length === 0) {
|
|
136
|
+
console.log(chalk.dim('No payments found.'));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const table = createTable(['Type', 'WAL', 'SUI Gas', 'Epochs', 'File', 'Date']);
|
|
141
|
+
for (const p of data.payments) {
|
|
142
|
+
table.push([
|
|
143
|
+
p.type,
|
|
144
|
+
p.walMist,
|
|
145
|
+
p.suiGasMist,
|
|
146
|
+
String(p.epochs),
|
|
147
|
+
chalk.dim(p.fileId.slice(0, 8) + '...'),
|
|
148
|
+
formatDate(p.createdAt),
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
console.log(table.toString());
|
|
152
|
+
console.log(chalk.dim(`\nTotal spent: ${data.totalSpentWalFormatted}`));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ── generate ─────────────────────────────────────────────────────
|
|
156
|
+
wallet.command('generate')
|
|
157
|
+
.description('Generate a new Sui Ed25519 keypair for use with shared vaults (SEAL encryption)')
|
|
158
|
+
.action(async () => {
|
|
159
|
+
const root = wallet.parent || wallet;
|
|
160
|
+
const format = root.opts().format || cliConfig.get('outputFormat');
|
|
161
|
+
|
|
162
|
+
const keypair = Ed25519Keypair.generate();
|
|
163
|
+
const address = keypair.toSuiAddress();
|
|
164
|
+
const privateKey = keypair.getSecretKey(); // bech32 suiprivkey1... format
|
|
165
|
+
|
|
166
|
+
if (format === 'json') {
|
|
167
|
+
console.log(JSON.stringify({ address, privateKey }, null, 2));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(chalk.bold('Generated Sui Ed25519 Keypair'));
|
|
172
|
+
console.log(` Address: ${chalk.cyan(address)}`);
|
|
173
|
+
console.log(` Private key: ${chalk.yellow(privateKey)}`);
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log(chalk.dim('Set TUSKYDP_SUI_PRIVATE_KEY=<private key> to use this keypair for shared vault encryption.'));
|
|
176
|
+
console.log(chalk.dim('Link the address to your Tusky account: tusky account link-sui ' + address));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Default action for `tusky wallet` with no subcommand → run info
|
|
180
|
+
wallet.action(async () => {
|
|
181
|
+
await wallet.commands.find(c => c.name() === 'info')?.parseAsync([], { from: 'user' });
|
|
182
|
+
});
|
|
183
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { cliConfig } from '../config.js';
|
|
5
|
+
import { getSDKClientFromParent } from '../sdk.js';
|
|
6
|
+
import { createTable, formatDate, shortId } from '../lib/output.js';
|
|
7
|
+
|
|
8
|
+
// Import canonical event list from shared — keep in sync
|
|
9
|
+
import { WEBHOOK_EVENTS } from '@tuskydp/shared/constants.js';
|
|
10
|
+
|
|
11
|
+
export function registerWebhookCommands(program: Command) {
|
|
12
|
+
const webhook = program.command('webhook').description('Manage webhook endpoints');
|
|
13
|
+
|
|
14
|
+
// ── create ──────────────────────────────────────────────────────────
|
|
15
|
+
webhook.command('create <url>')
|
|
16
|
+
.description('Create a webhook endpoint')
|
|
17
|
+
.requiredOption('--events <events>', 'Comma-separated list of events to subscribe to')
|
|
18
|
+
.option('--description <text>', 'Webhook description')
|
|
19
|
+
.action(async (url: string, options) => {
|
|
20
|
+
const sdk = getSDKClientFromParent(webhook);
|
|
21
|
+
const events = options.events.split(',').map((e: string) => e.trim());
|
|
22
|
+
|
|
23
|
+
const created = await sdk.webhooks.create({
|
|
24
|
+
url,
|
|
25
|
+
events: events as any,
|
|
26
|
+
description: options.description,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(chalk.green(`Webhook created: ${created.id}`));
|
|
30
|
+
console.log(`URL: ${created.url}`);
|
|
31
|
+
console.log(`Events: ${created.events.join(', ')}`);
|
|
32
|
+
if (created.secret) {
|
|
33
|
+
console.log(chalk.yellow.bold(`Secret: ${created.secret}`));
|
|
34
|
+
console.log(chalk.yellow(' Save this secret — it will not be shown again.'));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ── list ────────────────────────────────────────────────────────────
|
|
39
|
+
webhook.command('list')
|
|
40
|
+
.description('List webhook endpoints')
|
|
41
|
+
.action(async () => {
|
|
42
|
+
const sdk = getSDKClientFromParent(webhook);
|
|
43
|
+
const root = webhook.parent || webhook;
|
|
44
|
+
const format = root.opts().format || cliConfig.get('outputFormat');
|
|
45
|
+
const webhooks = await sdk.webhooks.list();
|
|
46
|
+
|
|
47
|
+
if (format === 'json') {
|
|
48
|
+
console.log(JSON.stringify(webhooks, null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (webhooks.length === 0) {
|
|
53
|
+
console.log(chalk.dim('No webhook endpoints found.'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const table = createTable(['URL', 'Events', 'Active', 'Failures', 'ID']);
|
|
58
|
+
for (const w of webhooks) {
|
|
59
|
+
table.push([
|
|
60
|
+
w.url.length > 40 ? w.url.slice(0, 39) + '...' : w.url,
|
|
61
|
+
String(w.events.length) + ' event(s)',
|
|
62
|
+
w.active ? chalk.green('Yes') : chalk.red('No'),
|
|
63
|
+
w.failureCount > 0 ? chalk.yellow(String(w.failureCount)) : '0',
|
|
64
|
+
chalk.dim(shortId(w.id)),
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
console.log(table.toString());
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ── info ────────────────────────────────────────────────────────────
|
|
71
|
+
webhook.command('info <webhook-id>')
|
|
72
|
+
.description('Show webhook endpoint details')
|
|
73
|
+
.action(async (webhookId: string) => {
|
|
74
|
+
const sdk = getSDKClientFromParent(webhook);
|
|
75
|
+
const root = webhook.parent || webhook;
|
|
76
|
+
const format = root.opts().format || cliConfig.get('outputFormat');
|
|
77
|
+
const w = await sdk.webhooks.get(webhookId);
|
|
78
|
+
|
|
79
|
+
if (format === 'json') {
|
|
80
|
+
console.log(JSON.stringify(w, null, 2));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`URL: ${w.url}`);
|
|
85
|
+
console.log(`Active: ${w.active ? chalk.green('Yes') : chalk.red('No')}`);
|
|
86
|
+
console.log(`Events: ${w.events.join(', ')}`);
|
|
87
|
+
console.log(`Description: ${w.description || chalk.dim('(none)')}`);
|
|
88
|
+
console.log(`Failures: ${w.failureCount}`);
|
|
89
|
+
if (w.lastDeliveryAt) {
|
|
90
|
+
console.log(`Last Delivery: ${formatDate(w.lastDeliveryAt)} (${w.lastDeliveryStatus})`);
|
|
91
|
+
}
|
|
92
|
+
console.log(`Created: ${new Date(w.createdAt).toLocaleString()}`);
|
|
93
|
+
console.log(`ID: ${w.id}`);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ── update ──────────────────────────────────────────────────────────
|
|
97
|
+
webhook.command('update <webhook-id>')
|
|
98
|
+
.description('Update a webhook endpoint')
|
|
99
|
+
.option('--url <url>', 'New webhook URL')
|
|
100
|
+
.option('--events <events>', 'Comma-separated list of events')
|
|
101
|
+
.option('--active <bool>', 'Enable or disable (true/false)')
|
|
102
|
+
.option('--description <text>', 'New description')
|
|
103
|
+
.action(async (webhookId: string, options) => {
|
|
104
|
+
const sdk = getSDKClientFromParent(webhook);
|
|
105
|
+
|
|
106
|
+
const params: { url?: string; events?: any[]; active?: boolean; description?: string } = {};
|
|
107
|
+
if (options.url) params.url = options.url;
|
|
108
|
+
if (options.events) params.events = options.events.split(',').map((e: string) => e.trim());
|
|
109
|
+
if (options.active !== undefined) params.active = options.active === 'true';
|
|
110
|
+
if (options.description) params.description = options.description;
|
|
111
|
+
|
|
112
|
+
if (Object.keys(params).length === 0) {
|
|
113
|
+
console.error(chalk.red('Provide at least one option to update (--url, --events, --active, --description).'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await sdk.webhooks.update(webhookId, params);
|
|
118
|
+
console.log(chalk.green('Webhook updated.'));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ── delete ──────────────────────────────────────────────────────────
|
|
122
|
+
webhook.command('delete <webhook-id>')
|
|
123
|
+
.description('Delete a webhook endpoint')
|
|
124
|
+
.option('--force', 'Skip confirmation prompt')
|
|
125
|
+
.action(async (webhookId: string, options) => {
|
|
126
|
+
const sdk = getSDKClientFromParent(webhook);
|
|
127
|
+
|
|
128
|
+
if (!options.force) {
|
|
129
|
+
const answers = await inquirer.prompt([{
|
|
130
|
+
type: 'confirm',
|
|
131
|
+
name: 'confirm',
|
|
132
|
+
message: 'Delete this webhook endpoint?',
|
|
133
|
+
default: false,
|
|
134
|
+
}]);
|
|
135
|
+
if (!answers.confirm) return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await sdk.webhooks.delete(webhookId);
|
|
139
|
+
console.log(chalk.green('Webhook deleted.'));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ── test ────────────────────────────────────────────────────────────
|
|
143
|
+
webhook.command('test <webhook-id>')
|
|
144
|
+
.description('Send a test delivery to the webhook')
|
|
145
|
+
.action(async (webhookId: string) => {
|
|
146
|
+
const sdk = getSDKClientFromParent(webhook);
|
|
147
|
+
const delivery = await sdk.webhooks.test(webhookId);
|
|
148
|
+
console.log(chalk.green('Test delivery sent.'));
|
|
149
|
+
console.log(`Delivery ID: ${delivery.id}`);
|
|
150
|
+
console.log(`Event: ${delivery.event}`);
|
|
151
|
+
console.log(`Status: ${delivery.status}`);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ── deliveries ──────────────────────────────────────────────────────
|
|
155
|
+
webhook.command('deliveries <webhook-id>')
|
|
156
|
+
.description('List recent webhook deliveries')
|
|
157
|
+
.option('--limit <n>', 'Max results', '20')
|
|
158
|
+
.action(async (webhookId: string, options) => {
|
|
159
|
+
const sdk = getSDKClientFromParent(webhook);
|
|
160
|
+
const root = webhook.parent || webhook;
|
|
161
|
+
const format = root.opts().format || cliConfig.get('outputFormat');
|
|
162
|
+
const deliveries = await sdk.webhooks.listDeliveries(webhookId, { limit: parseInt(options.limit) });
|
|
163
|
+
|
|
164
|
+
if (format === 'json') {
|
|
165
|
+
console.log(JSON.stringify(deliveries, null, 2));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (deliveries.length === 0) {
|
|
170
|
+
console.log(chalk.dim('No deliveries found.'));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const table = createTable(['Event', 'Status', 'HTTP', 'Attempts', 'Created', 'ID']);
|
|
175
|
+
const statusColors: Record<string, typeof chalk.green> = {
|
|
176
|
+
success: chalk.green,
|
|
177
|
+
failed: chalk.red,
|
|
178
|
+
pending: chalk.yellow,
|
|
179
|
+
};
|
|
180
|
+
for (const d of deliveries) {
|
|
181
|
+
const sc = statusColors[d.status] || chalk.white;
|
|
182
|
+
table.push([
|
|
183
|
+
d.event,
|
|
184
|
+
sc(d.status),
|
|
185
|
+
d.httpStatus ? String(d.httpStatus) : '-',
|
|
186
|
+
`${d.attempts}/${d.maxAttempts}`,
|
|
187
|
+
formatDate(d.createdAt),
|
|
188
|
+
chalk.dim(shortId(d.id)),
|
|
189
|
+
]);
|
|
190
|
+
}
|
|
191
|
+
console.log(table.toString());
|
|
192
|
+
});
|
|
193
|
+
}
|
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
|
-
|
|
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
|
-
*
|
|
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,28 +1,28 @@
|
|
|
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';
|
|
7
|
+
import { registerFolderCommands } from './commands/folder.js';
|
|
8
|
+
import { registerTrashCommands } from './commands/trash.js';
|
|
9
|
+
import { registerWebhookCommands } from './commands/webhook.js';
|
|
10
|
+
import { registerWalletCommands } from './commands/wallet.js';
|
|
8
11
|
import { uploadCommand } from './commands/upload.js';
|
|
9
12
|
import { downloadCommand } from './commands/download.js';
|
|
10
13
|
import { rehydrateCommand } from './commands/rehydrate.js';
|
|
11
14
|
import { registerTuiCommand } from './commands/tui.js';
|
|
12
15
|
import { registerMcpCommands } from './commands/mcp.js';
|
|
16
|
+
import { registerSuiCommands } from './commands/sui.js';
|
|
13
17
|
import { registerExportCommand } from './commands/export.js';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
import { createRequire } from 'module';
|
|
17
|
-
const __require = createRequire(import.meta.url);
|
|
18
|
-
const { version: VERSION } = __require('../../package.json');
|
|
18
|
+
import { CLI_VERSION } from './version.js';
|
|
19
19
|
|
|
20
20
|
const program = new Command();
|
|
21
21
|
|
|
22
22
|
program
|
|
23
23
|
.name('tusky')
|
|
24
24
|
.description('Tusky — Encrypted decentralized storage for developers, apps, and agents')
|
|
25
|
-
.version(
|
|
25
|
+
.version(CLI_VERSION)
|
|
26
26
|
.option('--api-key <key>', 'Override API key')
|
|
27
27
|
.option('--api-url <url>', 'Override API URL')
|
|
28
28
|
.option('--format <fmt>', 'Output format: table, json, plain', 'table')
|
|
@@ -38,20 +38,27 @@ program
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
registerAuthCommands(program);
|
|
41
|
-
registerEncryptionCommands(program);
|
|
42
41
|
registerVaultCommands(program);
|
|
43
42
|
registerFileCommands(program);
|
|
43
|
+
registerFolderCommands(program);
|
|
44
|
+
registerTrashCommands(program);
|
|
45
|
+
registerWebhookCommands(program);
|
|
46
|
+
registerWalletCommands(program);
|
|
44
47
|
registerAccountCommands(program);
|
|
45
48
|
registerTuiCommand(program);
|
|
46
49
|
registerMcpCommands(program);
|
|
50
|
+
registerSuiCommands(program);
|
|
47
51
|
registerExportCommand(program);
|
|
48
|
-
registerDecryptCommand(program);
|
|
49
52
|
|
|
50
53
|
// Direct shortcuts for common operations
|
|
51
|
-
program.command('upload
|
|
54
|
+
program.command('upload [paths...]')
|
|
52
55
|
.description('Upload files')
|
|
53
56
|
.option('--vault <vault>', 'Target vault')
|
|
57
|
+
.option('--folder <folder-id>', 'Target folder ID within the vault')
|
|
54
58
|
.option('--recursive', 'Upload directory contents')
|
|
59
|
+
.option('--content <text>', 'Upload inline text content instead of a file path')
|
|
60
|
+
.option('--stdin', 'Read file content from stdin')
|
|
61
|
+
.option('--name <filename>', 'File name to use (required with --content or --stdin)')
|
|
55
62
|
.action(async (paths: string[], options) => {
|
|
56
63
|
await uploadCommand(paths, options, program);
|
|
57
64
|
});
|
|
@@ -59,6 +66,7 @@ program.command('upload <paths...>')
|
|
|
59
66
|
program.command('download <file-id>')
|
|
60
67
|
.description('Download a file')
|
|
61
68
|
.option('--output <path>', 'Output path')
|
|
69
|
+
.option('--stdout', 'Write file content to stdout (for sandboxed agents without filesystem access)')
|
|
62
70
|
.action(async (fileId: string, options) => {
|
|
63
71
|
await downloadCommand(fileId, options, program);
|
|
64
72
|
});
|
|
@@ -66,6 +74,7 @@ program.command('download <file-id>')
|
|
|
66
74
|
program.command('rehydrate <blob-id>')
|
|
67
75
|
.description('Download a file directly from Walrus by blob ID')
|
|
68
76
|
.option('--output <path>', 'Output file path')
|
|
77
|
+
.option('--stdout', 'Write blob content to stdout (for sandboxed agents without filesystem access)')
|
|
69
78
|
.action(async (blobId: string, options) => {
|
|
70
79
|
await rehydrateCommand(blobId, options, program);
|
|
71
80
|
});
|
package/src/lib/resolve.ts
CHANGED
|
@@ -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
|
|
10
|
+
// Fall back to the first vault
|
|
11
11
|
const vaults = await sdk.vaults.list();
|
|
12
|
-
|
|
13
|
-
if (defaultV) return defaultV.id;
|
|
12
|
+
if (vaults.length > 0) return vaults[0].id;
|
|
14
13
|
|
|
15
|
-
throw new Error('No
|
|
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
|
package/src/mcp/context.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared context for MCP tool handlers.
|
|
3
3
|
*
|
|
4
|
-
* Holds the authenticated SDK client
|
|
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.
|
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
|
|
|
@@ -25,58 +19,7 @@ import { registerFolderTools } from './tools/folders.js';
|
|
|
25
19
|
import { registerFileTools } from './tools/files.js';
|
|
26
20
|
import { registerTrashTools } from './tools/trash.js';
|
|
27
21
|
import { registerSharedVaultTools } from './tools/sharedVaults.js';
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Encryption bootstrap
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Attempt to unlock the master key using (in order):
|
|
35
|
-
* 1. TUSKYDP_PASSWORD env var → derive wrapping key → unwrap master key
|
|
36
|
-
* 2. Existing session file (~/.tusky/session.enc)
|
|
37
|
-
*
|
|
38
|
-
* Returns the master key Buffer, or null if neither method works.
|
|
39
|
-
*/
|
|
40
|
-
async function unlockMasterKey(sdk: TuskyClient): Promise<Buffer | null> {
|
|
41
|
-
const password = process.env.TUSKYDP_PASSWORD;
|
|
42
|
-
|
|
43
|
-
if (password) {
|
|
44
|
-
try {
|
|
45
|
-
const params = await sdk.account.getEncryptionParams();
|
|
46
|
-
if (!params.setupComplete) {
|
|
47
|
-
console.error('[tusky-mcp] Encryption not set up on this account. Skipping encryption unlock.');
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const salt = Buffer.from(params.salt!, 'base64');
|
|
52
|
-
const verifier = Buffer.from(params.verifier!, 'base64');
|
|
53
|
-
const wrappingKey = deriveMasterKey(password, salt);
|
|
54
|
-
|
|
55
|
-
if (!verifyPassphrase(wrappingKey, verifier)) {
|
|
56
|
-
console.error('[tusky-mcp] TUSKYDP_PASSWORD is incorrect. Encryption will be unavailable.');
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (params.encryptedMasterKey) {
|
|
61
|
-
return unwrapMasterKey(Buffer.from(params.encryptedMasterKey, 'base64'), wrappingKey);
|
|
62
|
-
}
|
|
63
|
-
// Legacy accounts where wrapping key IS the master key
|
|
64
|
-
return wrappingKey;
|
|
65
|
-
} catch (err: any) {
|
|
66
|
-
console.error(`[tusky-mcp] Failed to unlock encryption: ${err.message}`);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Fallback: try existing session file
|
|
72
|
-
const sessionKey = loadMasterKey();
|
|
73
|
-
if (sessionKey) {
|
|
74
|
-
console.error('[tusky-mcp] Using encryption key from existing session.');
|
|
75
|
-
return sessionKey;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
22
|
+
import { CLI_VERSION } from '../version.js';
|
|
80
23
|
|
|
81
24
|
// ---------------------------------------------------------------------------
|
|
82
25
|
// Server lifecycle
|
|
@@ -106,15 +49,6 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
|
|
|
106
49
|
process.exit(1);
|
|
107
50
|
}
|
|
108
51
|
|
|
109
|
-
// Unlock encryption (best effort)
|
|
110
|
-
const masterKey = await unlockMasterKey(sdk);
|
|
111
|
-
if (masterKey) {
|
|
112
|
-
console.error('[tusky-mcp] Encryption unlocked — private vault operations are available.');
|
|
113
|
-
} else {
|
|
114
|
-
console.error('[tusky-mcp] Encryption not unlocked — private vault encrypt/decrypt unavailable.');
|
|
115
|
-
console.error('[tusky-mcp] Set TUSKYDP_PASSWORD env var or run `tusky encryption unlock` first.');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
52
|
// Load Sui keypair for SEAL shared vault operations (best effort)
|
|
119
53
|
const suiKeypair = getSuiKeypair();
|
|
120
54
|
if (suiKeypair) {
|
|
@@ -126,8 +60,6 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
|
|
|
126
60
|
// Build context
|
|
127
61
|
const ctx: McpContext = {
|
|
128
62
|
sdk,
|
|
129
|
-
getMasterKey: () => masterKey,
|
|
130
|
-
isEncryptionReady: () => masterKey !== null,
|
|
131
63
|
getSuiKeypair: () => suiKeypair,
|
|
132
64
|
isSealReady: () => suiKeypair !== null,
|
|
133
65
|
};
|
|
@@ -135,7 +67,7 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
|
|
|
135
67
|
// Create MCP server
|
|
136
68
|
const server = new McpServer({
|
|
137
69
|
name: 'tusky',
|
|
138
|
-
version:
|
|
70
|
+
version: CLI_VERSION,
|
|
139
71
|
});
|
|
140
72
|
|
|
141
73
|
// Register all tools
|
package/src/mcp/tools/account.ts
CHANGED
|
@@ -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
|
|
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
|
|