@exagent/agent 0.2.1 → 0.3.1

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/dist/cli.js CHANGED
@@ -1,57 +1,379 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AgentRuntime,
4
+ encryptSecretPayload,
5
+ getDefaultSecureStorePath,
4
6
  listTemplates,
5
7
  loadConfig,
8
+ readConfigFile,
9
+ writeConfigFile,
6
10
  writeSampleConfig
7
- } from "./chunk-B4VHIITU.js";
11
+ } from "./chunk-VDK4XPAC.js";
8
12
 
9
13
  // src/cli.ts
10
14
  import { Command } from "commander";
15
+
16
+ // src/setup.ts
17
+ import { chmodSync, existsSync, mkdirSync, writeFileSync } from "fs";
18
+ import { homedir } from "os";
19
+ import { dirname, resolve } from "path";
20
+ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
21
+ import * as clack from "@clack/prompts";
22
+
23
+ // src/ui.ts
24
+ import figlet from "figlet";
25
+ import gradient from "gradient-string";
26
+ import boxen from "boxen";
27
+ import pc from "picocolors";
28
+ import { createRequire } from "module";
29
+ var brandGradient = gradient(["#3B82F6", "#6366F1", "#7C3AED"]);
30
+ var accentGradient = gradient(["#22D3EE", "#3B82F6"]);
31
+ function getVersion() {
32
+ try {
33
+ const require2 = createRequire(import.meta.url);
34
+ const pkg = require2("../package.json");
35
+ return pkg.version || "0.0.0";
36
+ } catch {
37
+ return "0.0.0";
38
+ }
39
+ }
40
+ function printBanner() {
41
+ const art = figlet.textSync("EXAGENT", {
42
+ font: "Small",
43
+ horizontalLayout: "default"
44
+ });
45
+ console.log();
46
+ console.log(brandGradient(art));
47
+ console.log(pc.dim(` v${getVersion()}`));
48
+ console.log();
49
+ }
50
+ function printSuccess(title, lines) {
51
+ const body = [
52
+ "",
53
+ pc.bold(pc.white(title)),
54
+ "",
55
+ ...lines.map((l) => ` ${l}`),
56
+ ""
57
+ ].join("\n");
58
+ console.log();
59
+ console.log(boxen(body, {
60
+ padding: { top: 0, bottom: 0, left: 2, right: 2 },
61
+ borderColor: "#3B82F6",
62
+ borderStyle: "round",
63
+ dimBorder: false
64
+ }));
65
+ console.log();
66
+ }
67
+ function printStep(step, total, label) {
68
+ console.log();
69
+ console.log(accentGradient(` Step ${step} of ${total}`) + pc.dim(` \u2014 ${label}`));
70
+ }
71
+ function printDone(message) {
72
+ console.log(` ${pc.green("\u2713")} ${message}`);
73
+ }
74
+ function printInfo(message) {
75
+ console.log(` ${pc.dim("\u2502")} ${message}`);
76
+ }
77
+ function printError(message) {
78
+ console.log(` ${pc.red("\u2717")} ${message}`);
79
+ }
80
+
81
+ // src/setup.ts
82
+ function expandHomeDir(path) {
83
+ if (!path.startsWith("~/")) return path;
84
+ return resolve(homedir(), path.slice(2));
85
+ }
86
+ function cancelled() {
87
+ clack.cancel("Setup cancelled.");
88
+ process.exit(0);
89
+ }
90
+ async function consumeBootstrapPackage(config) {
91
+ if (!config.secrets?.bootstrapToken) {
92
+ return { apiToken: "" };
93
+ }
94
+ const apiHost = new URL(config.apiUrl).host;
95
+ printInfo(`Connecting to ${pc.cyan(apiHost)}...`);
96
+ const res = await fetch(`${config.apiUrl}/v1/agents/bootstrap/consume`, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({
100
+ agentId: config.agentId,
101
+ token: config.secrets.bootstrapToken
102
+ })
103
+ });
104
+ if (!res.ok) {
105
+ const body = await res.text();
106
+ throw new Error(`Failed to consume bootstrap package: ${body}`);
107
+ }
108
+ const data = await res.json();
109
+ return data.payload;
110
+ }
111
+ async function setupWallet(config) {
112
+ if (config.wallet?.privateKey) {
113
+ const account = privateKeyToAccount(config.wallet.privateKey);
114
+ printDone(`Using existing wallet: ${pc.dim(account.address)}`);
115
+ return config.wallet.privateKey;
116
+ }
117
+ const method = await clack.select({
118
+ message: "How would you like to set up your wallet?",
119
+ options: [
120
+ { value: "generate", label: "Generate new wallet locally", hint: "recommended" },
121
+ { value: "import", label: "Import existing private key" }
122
+ ]
123
+ });
124
+ if (clack.isCancel(method)) cancelled();
125
+ if (method === "generate") {
126
+ const privateKey2 = generatePrivateKey();
127
+ const address2 = privateKeyToAccount(privateKey2).address;
128
+ printDone(`Wallet created: ${pc.dim(address2)}`);
129
+ return privateKey2;
130
+ }
131
+ const privateKey = await clack.password({
132
+ message: "Wallet private key (0x...):",
133
+ validate: (val) => {
134
+ if (!/^0x[a-fA-F0-9]{64}$/.test(val)) {
135
+ return "Invalid private key. Expected a 32-byte hex string prefixed with 0x.";
136
+ }
137
+ }
138
+ });
139
+ if (clack.isCancel(privateKey)) cancelled();
140
+ const address = privateKeyToAccount(privateKey).address;
141
+ printDone(`Wallet imported: ${pc.dim(address)}`);
142
+ return privateKey;
143
+ }
144
+ var LLM_PROVIDERS = ["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"];
145
+ async function setupLlm(config, bootstrapPayload) {
146
+ let provider = config.llm?.provider || bootstrapPayload.llm?.provider;
147
+ if (provider) {
148
+ printInfo(`Provider: ${pc.cyan(provider)} ${pc.dim("(from dashboard)")}`);
149
+ } else {
150
+ const selected = await clack.select({
151
+ message: "LLM provider:",
152
+ options: LLM_PROVIDERS.map((p) => ({ value: p, label: p }))
153
+ });
154
+ if (clack.isCancel(selected)) cancelled();
155
+ provider = selected;
156
+ }
157
+ let model = config.llm?.model || bootstrapPayload.llm?.model;
158
+ if (model) {
159
+ printInfo(`Model: ${pc.cyan(model)} ${pc.dim("(from dashboard)")}`);
160
+ } else {
161
+ const entered = await clack.text({
162
+ message: "LLM model:",
163
+ placeholder: "gpt-4o",
164
+ validate: (val) => {
165
+ if (!val.trim()) return "Model name is required.";
166
+ }
167
+ });
168
+ if (clack.isCancel(entered)) cancelled();
169
+ model = entered;
170
+ }
171
+ let apiKey;
172
+ if (bootstrapPayload.llm?.apiKey) {
173
+ const useBootstrap = await clack.confirm({
174
+ message: "LLM API key received from dashboard. Use it?",
175
+ initialValue: true
176
+ });
177
+ if (clack.isCancel(useBootstrap)) cancelled();
178
+ if (useBootstrap) {
179
+ apiKey = bootstrapPayload.llm.apiKey;
180
+ }
181
+ }
182
+ if (!apiKey) {
183
+ apiKey = config.llm?.apiKey;
184
+ }
185
+ if (!apiKey) {
186
+ const entered = await clack.password({
187
+ message: "LLM API key:",
188
+ validate: (val) => {
189
+ if (!val.trim()) return "API key is required.";
190
+ }
191
+ });
192
+ if (clack.isCancel(entered)) cancelled();
193
+ apiKey = entered;
194
+ }
195
+ printDone("LLM configured");
196
+ return { provider, model, apiKey };
197
+ }
198
+ async function setupEncryption() {
199
+ printInfo(`Secrets encrypted with ${pc.cyan("AES-256-GCM")} (${pc.cyan("scrypt")} KDF)`);
200
+ printInfo("The password never leaves this machine.");
201
+ console.log();
202
+ const password2 = await clack.password({
203
+ message: "Choose a device password (12+ characters):",
204
+ validate: (val) => {
205
+ if (val.length < 12) return "Password must be at least 12 characters.";
206
+ }
207
+ });
208
+ if (clack.isCancel(password2)) cancelled();
209
+ const confirm2 = await clack.password({
210
+ message: "Confirm password:",
211
+ validate: (val) => {
212
+ if (val !== password2) return "Passwords do not match.";
213
+ }
214
+ });
215
+ if (clack.isCancel(confirm2)) cancelled();
216
+ return password2;
217
+ }
218
+ function writeSecureStore(path, secrets, password2) {
219
+ const secureStorePath = expandHomeDir(path);
220
+ const encrypted = encryptSecretPayload(secrets, password2);
221
+ const dir = dirname(secureStorePath);
222
+ if (!existsSync(dir)) {
223
+ mkdirSync(dir, { recursive: true, mode: 448 });
224
+ }
225
+ writeFileSync(secureStorePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
226
+ try {
227
+ chmodSync(secureStorePath, 384);
228
+ } catch {
229
+ }
230
+ return secureStorePath;
231
+ }
232
+ async function promptSecretPassword(question = "Device password:") {
233
+ const password2 = await clack.password({ message: question });
234
+ if (clack.isCancel(password2)) cancelled();
235
+ return password2;
236
+ }
237
+ async function ensureLocalSetup(configPath) {
238
+ const config = readConfigFile(configPath);
239
+ const existingSecureStorePath = config.secrets?.secureStorePath ? expandHomeDir(config.secrets.secureStorePath) : null;
240
+ if (existingSecureStorePath && !config.secrets?.bootstrapToken && existsSync(existingSecureStorePath) && !config.apiToken && !config.wallet?.privateKey && !config.llm.apiKey) {
241
+ return;
242
+ }
243
+ printBanner();
244
+ clack.intro(pc.bold("Agent Setup"));
245
+ printStep(1, 4, "Bootstrap package");
246
+ const bootstrapPayload = await consumeBootstrapPackage(config);
247
+ if (config.secrets?.bootstrapToken) {
248
+ printDone("Bootstrap package consumed");
249
+ if (bootstrapPayload.llm?.provider) {
250
+ printInfo(`LLM config received: ${pc.cyan(bootstrapPayload.llm.provider)}${bootstrapPayload.llm.model ? ` / ${pc.cyan(bootstrapPayload.llm.model)}` : ""}`);
251
+ }
252
+ } else {
253
+ printInfo("No bootstrap token \u2014 manual configuration");
254
+ }
255
+ printStep(2, 4, "Wallet setup");
256
+ const walletPrivateKey = await setupWallet(config);
257
+ printStep(3, 4, "LLM configuration");
258
+ const llm = await setupLlm(config, bootstrapPayload);
259
+ printStep(4, 4, "Device encryption");
260
+ const password2 = await setupEncryption();
261
+ const secrets = {
262
+ apiToken: bootstrapPayload.apiToken || config.apiToken || "",
263
+ walletPrivateKey,
264
+ llmApiKey: llm.apiKey
265
+ };
266
+ if (!secrets.apiToken) {
267
+ const token = await clack.password({
268
+ message: "Agent relay token:",
269
+ validate: (val) => {
270
+ if (!val.trim()) return "Relay token is required.";
271
+ }
272
+ });
273
+ if (clack.isCancel(token)) cancelled();
274
+ secrets.apiToken = token;
275
+ }
276
+ const nextConfig = structuredClone(config);
277
+ nextConfig.llm = {
278
+ ...nextConfig.llm,
279
+ provider: llm.provider,
280
+ model: llm.model
281
+ };
282
+ delete nextConfig.apiToken;
283
+ delete nextConfig.wallet;
284
+ delete nextConfig.llm.apiKey;
285
+ const secureStorePath = writeSecureStore(
286
+ nextConfig.secrets?.secureStorePath || getDefaultSecureStorePath(nextConfig.agentId),
287
+ secrets,
288
+ password2
289
+ );
290
+ nextConfig.secrets = { secureStorePath };
291
+ writeConfigFile(configPath, nextConfig);
292
+ printDone(`Encrypted store: ${pc.dim(secureStorePath)}`);
293
+ clack.outro(pc.green("Setup complete"));
294
+ printSuccess("Ready", [
295
+ `${pc.cyan("npx exagent run")} Start trading`,
296
+ `${pc.cyan("npx exagent status")} Check connection`,
297
+ "",
298
+ `${pc.dim("Dashboard:")} ${pc.cyan("https://exagent.io")}`
299
+ ]);
300
+ }
301
+
302
+ // src/cli.ts
11
303
  var program = new Command();
12
- program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.1.0");
304
+ program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.0");
13
305
  program.command("init").description("Create a sample agent configuration file").option("--agent-id <id>", "Agent ID (from dashboard)", "my-agent").option("--api-url <url>", "API server URL", "http://localhost:3002").option("--config <path>", "Config file path", "agent-config.json").action((opts) => {
306
+ printBanner();
14
307
  writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
15
- console.log(`Created ${opts.config}`);
16
- console.log("Edit the file with your API token, LLM key, and strategy settings.");
17
- console.log("Then run: exagent run");
308
+ printDone(`Created ${pc.cyan(opts.config)}`);
309
+ console.log();
310
+ console.log(` Edit only the public strategy/venue/risk settings.`);
311
+ console.log(` Then run: ${pc.cyan(`exagent setup --config ${opts.config}`)}`);
312
+ console.log();
313
+ });
314
+ program.command("setup").description("Run first-time secure local setup for agent secrets").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
315
+ try {
316
+ await ensureLocalSetup(opts.config);
317
+ } catch (err) {
318
+ printError(err.message);
319
+ process.exit(1);
320
+ }
18
321
  });
19
322
  program.command("run").description("Start the agent").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
20
323
  try {
21
- const config = loadConfig(opts.config);
324
+ await ensureLocalSetup(opts.config);
325
+ printBanner();
326
+ const config = await loadConfig(opts.config, {
327
+ getSecretPassword: async () => promptSecretPassword()
328
+ });
329
+ printDone(`Agent ${pc.cyan(config.agentId)} starting...`);
330
+ console.log();
22
331
  const runtime = new AgentRuntime(config);
23
332
  await runtime.start();
24
333
  await new Promise(() => {
25
334
  });
26
335
  } catch (err) {
27
- console.error("Failed to start agent:", err.message);
336
+ printError(err.message);
28
337
  process.exit(1);
29
338
  }
30
339
  });
31
340
  program.command("templates").description("List available strategy templates").action(() => {
341
+ printBanner();
32
342
  const templates = listTemplates();
33
- console.log("\nAvailable strategy templates:\n");
343
+ console.log(pc.bold(" Strategy Templates"));
344
+ console.log();
34
345
  for (const t of templates) {
35
- console.log(` ${t.id}`);
36
- console.log(` ${t.name} \u2014 ${t.description}`);
37
- console.log(` Risk: ${t.riskLevel} | Venues: ${t.venues.join(", ") || "any"}`);
346
+ console.log(` ${pc.cyan(t.id)}`);
347
+ console.log(` ${t.name} \u2014 ${pc.dim(t.description)}`);
348
+ console.log(` ${pc.dim(`Risk: ${t.riskLevel} | Venues: ${t.venues.join(", ") || "any"}`)}`);
38
349
  console.log();
39
350
  }
40
351
  });
41
352
  program.command("status").description("Check agent status").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
42
353
  try {
43
- const config = loadConfig(opts.config);
354
+ await ensureLocalSetup(opts.config);
355
+ printBanner();
356
+ const config = await loadConfig(opts.config, {
357
+ getSecretPassword: async () => promptSecretPassword()
358
+ });
44
359
  const res = await fetch(`${config.apiUrl}/v1/agents/${config.agentId}`, {
45
360
  headers: { Authorization: `Bearer ${config.apiToken}` }
46
361
  });
47
362
  if (!res.ok) {
48
- console.error(`API error: ${res.status}`);
363
+ printError(`API error: ${res.status}`);
49
364
  process.exit(1);
50
365
  }
51
366
  const agent = await res.json();
52
- console.log(JSON.stringify(agent, null, 2));
367
+ const name = agent.name || config.agentId;
368
+ const status = agent.status || "unknown";
369
+ const statusColor = status === "online" ? pc.green : status === "error" ? pc.red : pc.yellow;
370
+ printSuccess(name, [
371
+ `${pc.dim("Status:")} ${statusColor(status)}`,
372
+ `${pc.dim("Agent:")} ${pc.cyan(config.agentId)}`,
373
+ `${pc.dim("API:")} ${pc.dim(config.apiUrl)}`
374
+ ]);
53
375
  } catch (err) {
54
- console.error("Failed to check status:", err.message);
376
+ printError(err.message);
55
377
  process.exit(1);
56
378
  }
57
379
  });
package/dist/index.d.ts CHANGED
@@ -19,7 +19,14 @@ interface RuntimeConfig {
19
19
  };
20
20
  strategy: {
21
21
  file?: string;
22
+ code?: string;
22
23
  template?: string;
24
+ venues?: string[];
25
+ prompt?: {
26
+ name?: string;
27
+ systemPrompt: string;
28
+ venues?: string[];
29
+ };
23
30
  };
24
31
  trading: {
25
32
  mode: 'live' | 'paper';
@@ -78,7 +85,10 @@ interface RuntimeConfig {
78
85
  json?: boolean;
79
86
  };
80
87
  }
81
- declare function loadConfig(path?: string): RuntimeConfig;
88
+ interface LoadConfigOptions {
89
+ getSecretPassword?: () => Promise<string>;
90
+ }
91
+ declare function loadConfig(path?: string, options?: LoadConfigOptions): Promise<RuntimeConfig>;
82
92
  declare function generateSampleConfig(agentId: string, apiUrl: string): string;
83
93
  declare function writeSampleConfig(agentId: string, apiUrl: string, path?: string): void;
84
94
 
@@ -116,6 +126,18 @@ declare class AgentRuntime {
116
126
  constructor(config: RuntimeConfig);
117
127
  start(): Promise<void>;
118
128
  stop(): Promise<void>;
129
+ private configureLLMAdapter;
130
+ private teardownVenues;
131
+ private waitForCycleCompletion;
132
+ private getConfiguredSpotVenue;
133
+ private normalizeVenueForExecution;
134
+ private isVenueConfigured;
135
+ private canExecuteVenue;
136
+ private getPreferredExecutionVenues;
137
+ private buildRuntimeVenuesFromSelection;
138
+ private buildStrategyFallbackPrompt;
139
+ private extractStrategyConfigFromAgentConfig;
140
+ private applyExecutionMode;
119
141
  private initializeVenues;
120
142
  private startTrading;
121
143
  private stopTrading;
@@ -407,7 +429,13 @@ declare function createLLMAdapter(config: LLMConfig): LLMAdapter;
407
429
 
408
430
  declare function loadStrategy(config: {
409
431
  file?: string;
432
+ code?: string;
410
433
  template?: string;
434
+ prompt?: {
435
+ name?: string;
436
+ systemPrompt: string;
437
+ venues?: string[];
438
+ };
411
439
  }): Promise<StrategyFunction>;
412
440
 
413
441
  declare function getTemplate(id: string): StrategyTemplate | undefined;
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  loadConfig,
44
44
  loadStrategy,
45
45
  writeSampleConfig
46
- } from "./chunk-B4VHIITU.js";
46
+ } from "./chunk-VDK4XPAC.js";
47
47
  export {
48
48
  AcrossAdapter,
49
49
  AerodromeAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exagent/agent",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,24 +13,31 @@
13
13
  "types": "./dist/index.d.ts"
14
14
  }
15
15
  },
16
+ "scripts": {
17
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts",
18
+ "dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch"
19
+ },
16
20
  "dependencies": {
21
+ "@clack/prompts": "^1.1.0",
22
+ "@exagent/sdk": "workspace:*",
17
23
  "@polymarket/clob-client": "^4.0.0",
24
+ "boxen": "^8.0.1",
18
25
  "commander": "^12.0.0",
19
26
  "ethers": "^5.7.2",
27
+ "figlet": "^1.10.0",
28
+ "gradient-string": "^3.0.0",
29
+ "picocolors": "^1.1.1",
20
30
  "viem": "^2.21.0",
21
31
  "ws": "^8.16.0",
22
- "zod": "^3.22.0",
23
- "@exagent/sdk": "0.2.1"
32
+ "zod": "^3.22.0"
24
33
  },
25
34
  "devDependencies": {
35
+ "@types/figlet": "^1.7.0",
36
+ "@types/gradient-string": "^1.1.6",
26
37
  "@types/node": "^20.0.0",
27
38
  "@types/ws": "^8.5.0",
28
39
  "tsup": "^8.0.0",
29
40
  "tsx": "^4.0.0",
30
41
  "typescript": "^5.6.0"
31
- },
32
- "scripts": {
33
- "build": "tsup src/index.ts src/cli.ts --format esm --dts",
34
- "dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch"
35
42
  }
36
- }
43
+ }
package/src/cli.ts CHANGED
@@ -3,13 +3,15 @@ import { Command } from 'commander';
3
3
  import { loadConfig, writeSampleConfig } from './config.js';
4
4
  import { AgentRuntime } from './runtime.js';
5
5
  import { listTemplates } from './strategy/index.js';
6
+ import { ensureLocalSetup, promptSecretPassword } from './setup.js';
7
+ import { printBanner, printDone, printError, printSuccess, pc } from './ui.js';
6
8
 
7
9
  const program = new Command();
8
10
 
9
11
  program
10
12
  .name('exagent')
11
13
  .description('Exagent — LLM trading agent runtime')
12
- .version('0.1.0');
14
+ .version('0.3.0');
13
15
 
14
16
  program
15
17
  .command('init')
@@ -18,10 +20,26 @@ program
18
20
  .option('--api-url <url>', 'API server URL', 'http://localhost:3002')
19
21
  .option('--config <path>', 'Config file path', 'agent-config.json')
20
22
  .action((opts) => {
23
+ printBanner();
21
24
  writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
22
- console.log(`Created ${opts.config}`);
23
- console.log('Edit the file with your API token, LLM key, and strategy settings.');
24
- console.log('Then run: exagent run');
25
+ printDone(`Created ${pc.cyan(opts.config)}`);
26
+ console.log();
27
+ console.log(` Edit only the public strategy/venue/risk settings.`);
28
+ console.log(` Then run: ${pc.cyan(`exagent setup --config ${opts.config}`)}`);
29
+ console.log();
30
+ });
31
+
32
+ program
33
+ .command('setup')
34
+ .description('Run first-time secure local setup for agent secrets')
35
+ .option('--config <path>', 'Config file path', 'agent-config.json')
36
+ .action(async (opts) => {
37
+ try {
38
+ await ensureLocalSetup(opts.config);
39
+ } catch (err) {
40
+ printError((err as Error).message);
41
+ process.exit(1);
42
+ }
25
43
  });
26
44
 
27
45
  program
@@ -30,14 +48,20 @@ program
30
48
  .option('--config <path>', 'Config file path', 'agent-config.json')
31
49
  .action(async (opts) => {
32
50
  try {
33
- const config = loadConfig(opts.config);
51
+ await ensureLocalSetup(opts.config);
52
+ printBanner();
53
+ const config = await loadConfig(opts.config, {
54
+ getSecretPassword: async () => promptSecretPassword(),
55
+ });
56
+ printDone(`Agent ${pc.cyan(config.agentId)} starting...`);
57
+ console.log();
34
58
  const runtime = new AgentRuntime(config);
35
59
  await runtime.start();
36
60
 
37
61
  // Keep the process running
38
62
  await new Promise(() => {});
39
63
  } catch (err) {
40
- console.error('Failed to start agent:', (err as Error).message);
64
+ printError((err as Error).message);
41
65
  process.exit(1);
42
66
  }
43
67
  });
@@ -46,12 +70,14 @@ program
46
70
  .command('templates')
47
71
  .description('List available strategy templates')
48
72
  .action(() => {
73
+ printBanner();
49
74
  const templates = listTemplates();
50
- console.log('\nAvailable strategy templates:\n');
75
+ console.log(pc.bold(' Strategy Templates'));
76
+ console.log();
51
77
  for (const t of templates) {
52
- console.log(` ${t.id}`);
53
- console.log(` ${t.name} — ${t.description}`);
54
- console.log(` Risk: ${t.riskLevel} | Venues: ${t.venues.join(', ') || 'any'}`);
78
+ console.log(` ${pc.cyan(t.id)}`);
79
+ console.log(` ${t.name} — ${pc.dim(t.description)}`);
80
+ console.log(` ${pc.dim(`Risk: ${t.riskLevel} | Venues: ${t.venues.join(', ') || 'any'}`)}`);
55
81
  console.log();
56
82
  }
57
83
  });
@@ -62,20 +88,33 @@ program
62
88
  .option('--config <path>', 'Config file path', 'agent-config.json')
63
89
  .action(async (opts) => {
64
90
  try {
65
- const config = loadConfig(opts.config);
91
+ await ensureLocalSetup(opts.config);
92
+ printBanner();
93
+ const config = await loadConfig(opts.config, {
94
+ getSecretPassword: async () => promptSecretPassword(),
95
+ });
66
96
  const res = await fetch(`${config.apiUrl}/v1/agents/${config.agentId}`, {
67
97
  headers: { Authorization: `Bearer ${config.apiToken}` },
68
98
  });
69
99
 
70
100
  if (!res.ok) {
71
- console.error(`API error: ${res.status}`);
101
+ printError(`API error: ${res.status}`);
72
102
  process.exit(1);
73
103
  }
74
104
 
75
- const agent = await res.json();
76
- console.log(JSON.stringify(agent, null, 2));
105
+ const agent = await res.json() as Record<string, unknown>;
106
+ const name = (agent.name as string) || config.agentId;
107
+ const status = (agent.status as string) || 'unknown';
108
+
109
+ const statusColor = status === 'online' ? pc.green : status === 'error' ? pc.red : pc.yellow;
110
+
111
+ printSuccess(name, [
112
+ `${pc.dim('Status:')} ${statusColor(status)}`,
113
+ `${pc.dim('Agent:')} ${pc.cyan(config.agentId)}`,
114
+ `${pc.dim('API:')} ${pc.dim(config.apiUrl)}`,
115
+ ]);
77
116
  } catch (err) {
78
- console.error('Failed to check status:', (err as Error).message);
117
+ printError((err as Error).message);
79
118
  process.exit(1);
80
119
  }
81
120
  });