traderclaw-cli 1.0.51

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/bin/cli.ts ADDED
@@ -0,0 +1,629 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createInterface } from "readline";
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+
8
+ const VERSION = "1.0.0";
9
+ const PLUGIN_ID = "solana-trader";
10
+ const LEGACY_PLUGIN_IDS = ["traderclaw-v1", "solana-traderclaw-v1", "solana-traderclaw"];
11
+ const CONFIG_DIR = join(homedir(), ".openclaw");
12
+ const CONFIG_FILE = join(CONFIG_DIR, "openclaw.json");
13
+
14
+ const BANNER = `
15
+ ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗██╗ █████╗ ██╗ ██╗
16
+ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██╔══██╗██║ ██║
17
+ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║██║ ██║ ███████║██║ █╗ ██║
18
+ ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ ██╔══██╗██║███╗██║
19
+ ╚██████╔╝██║ ███████╗██║ ╚████║╚██████╗███████╗██║ ██║╚███╔███╔╝
20
+ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝
21
+ Solana Memecoin Trading Agent
22
+ `;
23
+
24
+ function print(msg: string) {
25
+ process.stdout.write(msg + "\n");
26
+ }
27
+
28
+ function printError(msg: string) {
29
+ process.stderr.write(`\x1b[31mError: ${msg}\x1b[0m\n`);
30
+ }
31
+
32
+ function printSuccess(msg: string) {
33
+ print(`\x1b[32m${msg}\x1b[0m`);
34
+ }
35
+
36
+ function printWarn(msg: string) {
37
+ print(`\x1b[33m${msg}\x1b[0m`);
38
+ }
39
+
40
+ function printInfo(msg: string) {
41
+ print(`\x1b[36m${msg}\x1b[0m`);
42
+ }
43
+
44
+ function maskKey(key: string): string {
45
+ if (key.length <= 8) return "****";
46
+ return key.slice(0, 4) + "..." + key.slice(-4);
47
+ }
48
+
49
+ async function prompt(question: string, defaultValue?: string): Promise<string> {
50
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
51
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
52
+ return new Promise((resolve) => {
53
+ rl.question(`${question}${suffix}: `, (answer) => {
54
+ rl.close();
55
+ resolve(answer.trim() || defaultValue || "");
56
+ });
57
+ });
58
+ }
59
+
60
+ async function confirm(question: string): Promise<boolean> {
61
+ const answer = await prompt(`${question} (y/n)`, "n");
62
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
63
+ }
64
+
65
+ async function httpRequest(url: string, opts: { method?: string; body?: unknown; apiKey?: string; timeout?: number } = {}): Promise<{ ok: boolean; status: number; data: unknown }> {
66
+ const controller = new AbortController();
67
+ const timeoutId = setTimeout(() => controller.abort(), opts.timeout ?? 10000);
68
+
69
+ try {
70
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
71
+ if (opts.apiKey) {
72
+ headers["Authorization"] = `Bearer ${opts.apiKey}`;
73
+ }
74
+
75
+ const fetchOpts: RequestInit = {
76
+ method: opts.method || "GET",
77
+ headers,
78
+ signal: controller.signal,
79
+ };
80
+
81
+ if (opts.body) {
82
+ fetchOpts.body = JSON.stringify(opts.body);
83
+ }
84
+
85
+ const res = await fetch(url, fetchOpts);
86
+ const text = await res.text();
87
+
88
+ let data: unknown;
89
+ try {
90
+ data = JSON.parse(text);
91
+ } catch {
92
+ data = { raw: text };
93
+ }
94
+
95
+ return { ok: res.ok, status: res.status, data };
96
+ } catch (err: unknown) {
97
+ if (err instanceof Error && err.name === "AbortError") {
98
+ throw new Error(`Request timed out after ${opts.timeout ?? 10000}ms`);
99
+ }
100
+ throw err;
101
+ } finally {
102
+ clearTimeout(timeoutId);
103
+ }
104
+ }
105
+
106
+ function readConfig(): Record<string, unknown> {
107
+ try {
108
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
109
+ return normalizePluginConfigShape(JSON.parse(raw));
110
+ } catch {
111
+ return {};
112
+ }
113
+ }
114
+
115
+ function writeConfig(config: Record<string, unknown>) {
116
+ normalizePluginConfigShape(config);
117
+ mkdirSync(CONFIG_DIR, { recursive: true });
118
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
119
+ }
120
+
121
+ function isRecord(value: unknown): value is Record<string, unknown> {
122
+ return !!value && typeof value === "object" && !Array.isArray(value);
123
+ }
124
+
125
+ function normalizePluginConfigShape(config: Record<string, unknown>): Record<string, unknown> {
126
+ if (!isRecord(config)) return config;
127
+ if (!isRecord(config.plugins)) config.plugins = {};
128
+ const plugins = config.plugins as Record<string, unknown>;
129
+ if (!isRecord(plugins.entries)) plugins.entries = {};
130
+ const entries = plugins.entries as Record<string, unknown>;
131
+
132
+ let enabledSeen = false;
133
+ let enabledValue = false;
134
+ let mergedConfig: Record<string, unknown> = {};
135
+ let found = false;
136
+
137
+ for (const sourceId of [...LEGACY_PLUGIN_IDS, PLUGIN_ID]) {
138
+ const entry = entries[sourceId];
139
+ if (!isRecord(entry)) continue;
140
+ found = true;
141
+ if (typeof entry.enabled === "boolean") {
142
+ enabledSeen = true;
143
+ enabledValue = enabledValue || entry.enabled;
144
+ }
145
+ if (isRecord(entry.config)) {
146
+ mergedConfig = { ...mergedConfig, ...(entry.config as Record<string, unknown>) };
147
+ }
148
+ }
149
+
150
+ if (found) {
151
+ const canonical = isRecord(entries[PLUGIN_ID]) ? entries[PLUGIN_ID] as Record<string, unknown> : {};
152
+ entries[PLUGIN_ID] = {
153
+ ...canonical,
154
+ enabled: typeof canonical.enabled === "boolean" ? canonical.enabled : (enabledSeen ? enabledValue : true),
155
+ config: mergedConfig,
156
+ };
157
+ }
158
+
159
+ for (const legacyId of LEGACY_PLUGIN_IDS) {
160
+ delete entries[legacyId];
161
+ }
162
+
163
+ if (Array.isArray(plugins.allow)) {
164
+ const seen = new Set<string>();
165
+ plugins.allow = plugins.allow.filter((id): id is string => {
166
+ if (typeof id !== "string") return false;
167
+ const trimmed = id.trim();
168
+ if (!trimmed || LEGACY_PLUGIN_IDS.includes(trimmed) || seen.has(trimmed)) return false;
169
+ seen.add(trimmed);
170
+ return true;
171
+ });
172
+ }
173
+
174
+ return config;
175
+ }
176
+
177
+ function getPluginConfig(config: Record<string, unknown>): Record<string, unknown> | null {
178
+ normalizePluginConfigShape(config);
179
+ const plugins = config.plugins as Record<string, unknown> | undefined;
180
+ if (!plugins) return null;
181
+ const entries = plugins.entries as Record<string, unknown> | undefined;
182
+ if (!entries) return null;
183
+ const plugin = entries[PLUGIN_ID] as Record<string, unknown> | undefined;
184
+ if (!plugin) return null;
185
+ return (plugin.config as Record<string, unknown>) || null;
186
+ }
187
+
188
+ function setPluginConfig(config: Record<string, unknown>, pluginConfig: Record<string, unknown>) {
189
+ normalizePluginConfigShape(config);
190
+ if (!config.plugins) config.plugins = {};
191
+ const plugins = config.plugins as Record<string, unknown>;
192
+ if (!plugins.entries) plugins.entries = {};
193
+ const entries = plugins.entries as Record<string, unknown>;
194
+ entries[PLUGIN_ID] = {
195
+ enabled: true,
196
+ config: pluginConfig,
197
+ };
198
+ }
199
+
200
+ async function cmdSetup(args: string[]) {
201
+ print(BANNER);
202
+ printInfo("Welcome to OpenClaw Solana Trader setup.\n");
203
+
204
+ let apiKey = "";
205
+ let orchestratorUrl = "";
206
+
207
+ for (let i = 0; i < args.length; i++) {
208
+ if ((args[i] === "--api-key" || args[i] === "-k") && args[i + 1]) {
209
+ apiKey = args[++i];
210
+ }
211
+ if ((args[i] === "--url" || args[i] === "-u") && args[i + 1]) {
212
+ orchestratorUrl = args[++i];
213
+ }
214
+ }
215
+
216
+ if (!apiKey) {
217
+ print("You need an API key to connect to the OpenClaw orchestrator.");
218
+ print("Get one at: https://traderclaw.ai/register\n");
219
+ apiKey = await prompt("Enter your API key");
220
+ }
221
+
222
+ if (!apiKey) {
223
+ printError("API key is required. Get one at https://traderclaw.ai/register");
224
+ process.exit(1);
225
+ }
226
+
227
+ if (!orchestratorUrl) {
228
+ orchestratorUrl = await prompt("Orchestrator URL", "https://api.traderclaw.ai");
229
+ }
230
+
231
+ orchestratorUrl = orchestratorUrl.replace(/\/+$/, "");
232
+
233
+ print("\nValidating connection...\n");
234
+
235
+ let healthOk = false;
236
+ try {
237
+ const health = await httpRequest(`${orchestratorUrl}/healthz`, { apiKey });
238
+ if (health.ok) {
239
+ const h = health.data as Record<string, unknown>;
240
+ printSuccess(` Orchestrator reachable`);
241
+ printInfo(` Service: ${h.service || "unknown"}`);
242
+ printInfo(` Execution mode: ${h.executionMode || "unknown"}`);
243
+ printInfo(` Upstream configured: ${h.upstreamConfigured ? "yes" : "no"}`);
244
+ healthOk = true;
245
+ } else {
246
+ if (health.status === 401 || health.status === 403) {
247
+ printError("Invalid API key. Check your key and try again.");
248
+ process.exit(1);
249
+ }
250
+ printError(`Orchestrator returned HTTP ${health.status}`);
251
+ process.exit(1);
252
+ }
253
+ } catch (err) {
254
+ printError(`Cannot reach orchestrator at ${orchestratorUrl}`);
255
+ printError(err instanceof Error ? err.message : String(err));
256
+ print("\nTips:");
257
+ print(" - Check the URL is correct");
258
+ print(" - Make sure the orchestrator is running");
259
+ print(` - Try: curl ${orchestratorUrl}/healthz`);
260
+ process.exit(1);
261
+ }
262
+
263
+ let systemOk = false;
264
+ try {
265
+ const status = await httpRequest(`${orchestratorUrl}/api/system/status`, { apiKey });
266
+ if (status.ok) {
267
+ const s = status.data as Record<string, unknown>;
268
+ printSuccess(` System status OK`);
269
+ if (s.walletCount !== undefined) printInfo(` Wallets: ${s.walletCount}`);
270
+ if (s.wsConnections !== undefined) printInfo(` WebSocket connections: ${s.wsConnections}`);
271
+ systemOk = true;
272
+ } else {
273
+ printWarn(" System status check returned non-OK (non-critical)");
274
+ }
275
+ } catch {
276
+ printWarn(" System status check failed (non-critical)");
277
+ }
278
+
279
+ print("\nSetting up wallet...\n");
280
+
281
+ let walletId: string | null = null;
282
+ let walletLabel = "";
283
+
284
+ try {
285
+ const walletsRes = await httpRequest(`${orchestratorUrl}/api/wallets`, { apiKey });
286
+ if (walletsRes.ok && Array.isArray(walletsRes.data) && (walletsRes.data as unknown[]).length > 0) {
287
+ const wallets = walletsRes.data as Array<Record<string, unknown>>;
288
+ printInfo(` Found ${wallets.length} existing wallet(s):`);
289
+ wallets.forEach((w, i) => {
290
+ print(` ${i + 1}. ${w.label || "Unnamed"} (ID: ${w.id}, Status: ${w.status})`);
291
+ });
292
+
293
+ const choice = await prompt("\nUse existing wallet? Enter number or 'new' to create one", "1");
294
+
295
+ if (choice.toLowerCase() === "new") {
296
+ walletLabel = await prompt("Wallet label", "Trading Wallet");
297
+ const createRes = await httpRequest(`${orchestratorUrl}/api/wallet/create`, {
298
+ method: "POST",
299
+ body: { label: walletLabel, strategyProfile: "aggressive" },
300
+ apiKey,
301
+ });
302
+ if (createRes.ok) {
303
+ const created = createRes.data as Record<string, unknown>;
304
+ walletId = String(created.id);
305
+ printSuccess(` Wallet created (ID: ${walletId})`);
306
+ } else {
307
+ printError("Failed to create wallet");
308
+ printError(JSON.stringify(createRes.data));
309
+ process.exit(1);
310
+ }
311
+ } else {
312
+ const idx = parseInt(choice, 10) - 1;
313
+ if (idx >= 0 && idx < wallets.length) {
314
+ walletId = String(wallets[idx].id);
315
+ walletLabel = wallets[idx].label as string || "Unnamed";
316
+ printSuccess(` Using wallet: ${walletLabel} (ID: ${walletId})`);
317
+ } else {
318
+ walletId = String(wallets[0].id);
319
+ walletLabel = wallets[0].label as string || "Unnamed";
320
+ printSuccess(` Using wallet: ${walletLabel} (ID: ${walletId})`);
321
+ }
322
+ }
323
+ } else {
324
+ printInfo(" No existing wallets found. Creating one...");
325
+ walletLabel = await prompt("Wallet label", "Trading Wallet");
326
+ const createRes = await httpRequest(`${orchestratorUrl}/api/wallet/create`, {
327
+ method: "POST",
328
+ body: { label: walletLabel, strategyProfile: "aggressive" },
329
+ apiKey,
330
+ });
331
+ if (createRes.ok) {
332
+ const created = createRes.data as Record<string, unknown>;
333
+ walletId = String(created.id);
334
+ printSuccess(` Wallet created (ID: ${walletId})`);
335
+ } else {
336
+ printError("Failed to create wallet");
337
+ printError(JSON.stringify(createRes.data));
338
+ process.exit(1);
339
+ }
340
+ }
341
+ } catch (err) {
342
+ printError("Failed to set up wallet");
343
+ printError(err instanceof Error ? err.message : String(err));
344
+ process.exit(1);
345
+ }
346
+
347
+ print("\nWriting configuration...\n");
348
+
349
+ const existingConfig = readConfig();
350
+ const pluginConfig: Record<string, unknown> = {
351
+ orchestratorUrl,
352
+ walletId,
353
+ apiKey,
354
+ apiTimeout: 120000,
355
+ };
356
+ setPluginConfig(existingConfig, pluginConfig);
357
+ writeConfig(existingConfig);
358
+
359
+ printSuccess(` Config written to ${CONFIG_FILE}`);
360
+
361
+ print("\n" + "=".repeat(60));
362
+ printSuccess("\n Setup complete!\n");
363
+ print("=".repeat(60));
364
+ print(`
365
+ Orchestrator: ${orchestratorUrl}
366
+ Wallet: ${walletLabel} (ID: ${walletId})
367
+ API Key: ${maskKey(apiKey)}
368
+ Config: ${CONFIG_FILE}
369
+ `);
370
+ print("Next steps:");
371
+ print(" 1. Install the plugin: openclaw plugins install solana-traderclaw (or: npm install -g solana-traderclaw)");
372
+ print(" 2. Restart the gateway: openclaw gateway --restart");
373
+ print(" 3. Start trading: Ask OpenClaw to scan for opportunities");
374
+ print("");
375
+ print("Quick commands:");
376
+ print(" openclaw-trader status Check connection health");
377
+ print(" openclaw-trader config View current configuration");
378
+ print("");
379
+ }
380
+
381
+ async function cmdStatus() {
382
+ const config = readConfig();
383
+ const pluginConfig = getPluginConfig(config);
384
+
385
+ if (!pluginConfig) {
386
+ printError("No plugin configuration found. Run 'openclaw-trader setup' first.");
387
+ process.exit(1);
388
+ }
389
+
390
+ const orchestratorUrl = pluginConfig.orchestratorUrl as string;
391
+ const walletId = pluginConfig.walletId as string;
392
+ const apiKey = pluginConfig.apiKey as string | undefined;
393
+
394
+ if (!orchestratorUrl) {
395
+ printError("orchestratorUrl not set in config. Run 'openclaw-trader setup' to fix.");
396
+ process.exit(1);
397
+ }
398
+
399
+ print("\nOpenClaw Solana Trader - Status\n");
400
+ print("=".repeat(45));
401
+
402
+ try {
403
+ const health = await httpRequest(`${orchestratorUrl}/healthz`, { apiKey, timeout: 5000 });
404
+ if (health.ok) {
405
+ const h = health.data as Record<string, unknown>;
406
+ printSuccess(" Orchestrator: CONNECTED");
407
+ printInfo(` Execution mode: ${h.executionMode || "unknown"}`);
408
+ printInfo(` Upstream: ${h.upstreamConfigured ? "configured" : "not configured"}`);
409
+ } else {
410
+ printError(` Orchestrator: ERROR (HTTP ${health.status})`);
411
+ }
412
+ } catch (err) {
413
+ printError(" Orchestrator: UNREACHABLE");
414
+ printError(` ${err instanceof Error ? err.message : String(err)}`);
415
+ }
416
+
417
+ try {
418
+ const statusRes = await httpRequest(`${orchestratorUrl}/api/system/status`, { apiKey, timeout: 5000 });
419
+ if (statusRes.ok) {
420
+ const s = statusRes.data as Record<string, unknown>;
421
+ printSuccess(" System status: OK");
422
+ if (s.wsConnections !== undefined) printInfo(` WS connections: ${s.wsConnections}`);
423
+ }
424
+ } catch {
425
+ printWarn(" System status: unavailable");
426
+ }
427
+
428
+ print("");
429
+
430
+ try {
431
+ const capitalRes = await httpRequest(`${orchestratorUrl}/api/capital/status?walletId=${walletId}`, { apiKey, timeout: 5000 });
432
+ if (capitalRes.ok) {
433
+ const c = capitalRes.data as Record<string, unknown>;
434
+ printSuccess(" Wallet: ACTIVE");
435
+ printInfo(` Wallet ID: ${walletId}`);
436
+ printInfo(` Balance: ${c.balanceSol ?? "?"} SOL`);
437
+ printInfo(` Open positions: ${c.openPositionCount ?? "?"}`);
438
+ printInfo(` Unrealized PnL: ${c.totalUnrealizedPnl ?? "?"} SOL`);
439
+ printInfo(` Daily loss: ${c.dailyLossSol ?? 0} SOL`);
440
+ } else {
441
+ printError(" Wallet: ERROR");
442
+ }
443
+ } catch {
444
+ printWarn(" Wallet: unavailable");
445
+ }
446
+
447
+ try {
448
+ const ksRes = await httpRequest(`${orchestratorUrl}/api/killswitch/status?walletId=${walletId}`, { apiKey, timeout: 5000 });
449
+ if (ksRes.ok) {
450
+ const ks = ksRes.data as Record<string, unknown>;
451
+ if (ks.enabled) {
452
+ printWarn(` Kill switch: ENABLED (${ks.mode})`);
453
+ } else {
454
+ printInfo(` Kill switch: disabled`);
455
+ }
456
+ }
457
+ } catch {
458
+ /* skip */
459
+ }
460
+
461
+ try {
462
+ const stratRes = await httpRequest(`${orchestratorUrl}/api/strategy/state?walletId=${walletId}`, { apiKey, timeout: 5000 });
463
+ if (stratRes.ok) {
464
+ const st = stratRes.data as Record<string, unknown>;
465
+ printInfo(` Strategy version: ${st.strategyVersion || "?"}`);
466
+ printInfo(` Mode: ${st.mode || "HARDENED"}`);
467
+ }
468
+ } catch {
469
+ /* skip */
470
+ }
471
+
472
+ print("\n" + "=".repeat(45));
473
+ print("");
474
+ }
475
+
476
+ async function cmdConfig(subArgs: string[]) {
477
+ const subCmd = subArgs[0] || "show";
478
+
479
+ if (subCmd === "show") {
480
+ const config = readConfig();
481
+ const pluginConfig = getPluginConfig(config);
482
+
483
+ if (!pluginConfig) {
484
+ printError("No plugin configuration found. Run 'openclaw-trader setup' first.");
485
+ process.exit(1);
486
+ }
487
+
488
+ print("\nOpenClaw Solana Trader - Configuration\n");
489
+ print("=".repeat(45));
490
+ print(` Config file: ${CONFIG_FILE}`);
491
+ print(` Orchestrator URL: ${pluginConfig.orchestratorUrl || "not set"}`);
492
+ print(` Wallet ID: ${pluginConfig.walletId ?? "not set"}`);
493
+ print(` API Key: ${pluginConfig.apiKey ? maskKey(pluginConfig.apiKey as string) : "not set"}`);
494
+ print(` API Timeout: ${pluginConfig.apiTimeout || 120000}ms`);
495
+ print("=".repeat(45));
496
+ print("");
497
+ return;
498
+ }
499
+
500
+ if (subCmd === "set") {
501
+ const key = subArgs[1];
502
+ const value = subArgs[2];
503
+
504
+ if (!key || !value) {
505
+ printError("Usage: openclaw-trader config set <key> <value>");
506
+ print(" Available keys: orchestratorUrl, walletId, apiKey, apiTimeout");
507
+ process.exit(1);
508
+ }
509
+
510
+ const allowedKeys = ["orchestratorUrl", "walletId", "apiKey", "apiTimeout"];
511
+ if (!allowedKeys.includes(key)) {
512
+ printError(`Unknown config key: ${key}`);
513
+ print(` Available keys: ${allowedKeys.join(", ")}`);
514
+ process.exit(1);
515
+ }
516
+
517
+ const config = readConfig();
518
+ const pluginConfig = getPluginConfig(config) || {};
519
+
520
+ let parsedValue: unknown = value;
521
+ if (key === "walletId") {
522
+ parsedValue = value;
523
+ }
524
+ if (key === "apiTimeout") {
525
+ parsedValue = parseInt(value, 10);
526
+ if (isNaN(parsedValue as number)) {
527
+ printError("apiTimeout must be a number (milliseconds)");
528
+ process.exit(1);
529
+ }
530
+ }
531
+
532
+ pluginConfig[key] = parsedValue;
533
+ setPluginConfig(config, pluginConfig);
534
+ writeConfig(config);
535
+
536
+ printSuccess(`Set ${key} = ${key === "apiKey" ? maskKey(value) : value}`);
537
+ print("Restart the gateway for changes to take effect: openclaw gateway --restart");
538
+ return;
539
+ }
540
+
541
+ if (subCmd === "reset") {
542
+ const confirmed = await confirm("This will remove all OpenClaw Solana Trader configuration. Continue?");
543
+ if (!confirmed) {
544
+ print("Cancelled.");
545
+ return;
546
+ }
547
+
548
+ const config = readConfig();
549
+ const plugins = config.plugins as Record<string, unknown> | undefined;
550
+ if (plugins) {
551
+ const entries = plugins.entries as Record<string, unknown> | undefined;
552
+ if (entries) {
553
+ delete entries[PLUGIN_ID];
554
+ }
555
+ }
556
+ writeConfig(config);
557
+ printSuccess("Plugin configuration removed.");
558
+ return;
559
+ }
560
+
561
+ printError(`Unknown config subcommand: ${subCmd}`);
562
+ print(" Available: show, set, reset");
563
+ process.exit(1);
564
+ }
565
+
566
+ function printHelp() {
567
+ print(`
568
+ OpenClaw Solana Trader CLI v${VERSION}
569
+
570
+ Usage: openclaw-trader <command> [options]
571
+
572
+ Commands:
573
+ setup Set up the plugin (API key, orchestrator, wallet)
574
+ status Check connection health and wallet status
575
+ config View and manage configuration
576
+
577
+ Setup options:
578
+ --api-key, -k API key (skip interactive prompt)
579
+ --url, -u Orchestrator URL (skip interactive prompt)
580
+
581
+ Config subcommands:
582
+ config show Show current configuration
583
+ config set <k> <v> Update a configuration value
584
+ config reset Remove plugin configuration
585
+
586
+ Examples:
587
+ openclaw-trader setup
588
+ openclaw-trader setup --api-key sk_live_abc123 --url https://api.traderclaw.ai
589
+ openclaw-trader status
590
+ openclaw-trader config show
591
+ openclaw-trader config set apiTimeout 60000
592
+ `);
593
+ }
594
+
595
+ async function main() {
596
+ const args = process.argv.slice(2);
597
+ const command = args[0];
598
+
599
+ if (!command || command === "--help" || command === "-h") {
600
+ printHelp();
601
+ process.exit(0);
602
+ }
603
+
604
+ if (command === "--version" || command === "-v") {
605
+ print(`openclaw-trader v${VERSION}`);
606
+ process.exit(0);
607
+ }
608
+
609
+ switch (command) {
610
+ case "setup":
611
+ await cmdSetup(args.slice(1));
612
+ break;
613
+ case "status":
614
+ await cmdStatus();
615
+ break;
616
+ case "config":
617
+ await cmdConfig(args.slice(1));
618
+ break;
619
+ default:
620
+ printError(`Unknown command: ${command}`);
621
+ printHelp();
622
+ process.exit(1);
623
+ }
624
+ }
625
+
626
+ main().catch((err) => {
627
+ printError(err instanceof Error ? err.message : String(err));
628
+ process.exit(1);
629
+ });