@exagent/agent 0.2.1 → 0.3.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/dist/cli.js CHANGED
@@ -1,24 +1,220 @@
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-UAP5CTHB.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 { createInterface } from "readline/promises";
21
+ import { stdin as input, stdout as output } from "process";
22
+ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
23
+ function expandHomeDir(path) {
24
+ if (!path.startsWith("~/")) return path;
25
+ return resolve(homedir(), path.slice(2));
26
+ }
27
+ async function prompt(question) {
28
+ const rl = createInterface({ input, output });
29
+ try {
30
+ return (await rl.question(question)).trim();
31
+ } finally {
32
+ rl.close();
33
+ }
34
+ }
35
+ async function promptSecret(question) {
36
+ const rl = createInterface({ input, output, terminal: true });
37
+ rl.stdoutMuted = true;
38
+ const originalWrite = rl._writeToOutput?.bind(rl);
39
+ rl._writeToOutput = (text) => {
40
+ if (!rl.stdoutMuted) {
41
+ originalWrite?.(text);
42
+ }
43
+ };
44
+ try {
45
+ const answer = (await rl.question(question)).trim();
46
+ output.write("\n");
47
+ return answer;
48
+ } finally {
49
+ rl.close();
50
+ }
51
+ }
52
+ async function promptSecretPassword(question = "Device password: ") {
53
+ return promptSecret(question);
54
+ }
55
+ async function promptPasswordWithConfirmation() {
56
+ output.write("\n");
57
+ output.write("Important: this password encrypts the local wallet key, relay token, and agent LLM key for this device.\n");
58
+ output.write("If you lose this password and the agent wallet holds funds, those funds cannot be recovered.\n\n");
59
+ const ack = await prompt('Type "I UNDERSTAND" to continue: ');
60
+ if (ack !== "I UNDERSTAND") {
61
+ throw new Error("Secure setup aborted");
62
+ }
63
+ while (true) {
64
+ const password = await promptSecret("Create a device password (min 12 chars): ");
65
+ if (password.length < 12) {
66
+ output.write("Password must be at least 12 characters.\n");
67
+ continue;
68
+ }
69
+ const confirm = await promptSecret("Confirm device password: ");
70
+ if (password !== confirm) {
71
+ output.write("Passwords did not match. Try again.\n");
72
+ continue;
73
+ }
74
+ return password;
75
+ }
76
+ }
77
+ async function promptWalletPrivateKey() {
78
+ while (true) {
79
+ const choice = (await prompt("Wallet setup \u2014 [1] generate new wallet locally, [2] use existing private key: ")).trim();
80
+ if (choice === "1") {
81
+ const privateKey = generatePrivateKey();
82
+ const address = privateKeyToAccount(privateKey).address;
83
+ output.write(`Generated wallet address: ${address}
84
+ `);
85
+ return privateKey;
86
+ }
87
+ if (choice === "2") {
88
+ const privateKey = await promptSecret("Wallet private key (0x...): ");
89
+ if (/^0x[a-fA-F0-9]{64}$/.test(privateKey)) {
90
+ return privateKey;
91
+ }
92
+ output.write("Invalid private key. Expected a 32-byte hex string prefixed with 0x.\n");
93
+ continue;
94
+ }
95
+ output.write("Enter 1 or 2.\n");
96
+ }
97
+ }
98
+ async function promptLlmProvider() {
99
+ while (true) {
100
+ const provider = (await prompt("Agent LLM provider (openai/anthropic/google/deepseek/mistral/groq/together/ollama): ")).toLowerCase();
101
+ if (["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"].includes(provider)) {
102
+ return provider;
103
+ }
104
+ output.write("Unsupported provider.\n");
105
+ }
106
+ }
107
+ async function consumeBootstrapPackage(config) {
108
+ if (!config.secrets?.bootstrapToken) {
109
+ return { apiToken: "" };
110
+ }
111
+ const res = await fetch(`${config.apiUrl}/v1/agents/bootstrap/consume`, {
112
+ method: "POST",
113
+ headers: { "Content-Type": "application/json" },
114
+ body: JSON.stringify({
115
+ agentId: config.agentId,
116
+ token: config.secrets.bootstrapToken
117
+ })
118
+ });
119
+ if (!res.ok) {
120
+ const body = await res.text();
121
+ throw new Error(`Failed to consume secure setup package: ${body}`);
122
+ }
123
+ const data = await res.json();
124
+ return data.payload;
125
+ }
126
+ async function buildLocalSecrets(config, bootstrapPayload) {
127
+ const nextConfig = structuredClone(config);
128
+ const llm = { ...nextConfig.llm || {} };
129
+ if (bootstrapPayload.llm?.provider && !llm.provider) {
130
+ llm.provider = bootstrapPayload.llm.provider;
131
+ }
132
+ if (bootstrapPayload.llm?.model && !llm.model) {
133
+ llm.model = bootstrapPayload.llm.model;
134
+ }
135
+ if (!llm.provider) {
136
+ llm.provider = await promptLlmProvider();
137
+ }
138
+ if (!llm.model) {
139
+ llm.model = await prompt("Agent LLM model: ");
140
+ if (!llm.model) {
141
+ throw new Error("Agent LLM model is required");
142
+ }
143
+ }
144
+ const secrets = {
145
+ apiToken: bootstrapPayload.apiToken || nextConfig.apiToken || await promptSecret("Agent relay token: "),
146
+ walletPrivateKey: bootstrapPayload.walletPrivateKey || nextConfig.wallet?.privateKey || await promptWalletPrivateKey(),
147
+ llmApiKey: bootstrapPayload.llm?.apiKey || nextConfig.llm.apiKey || await promptSecret("Agent LLM API key: ")
148
+ };
149
+ if (!secrets.apiToken) {
150
+ throw new Error("Agent relay token is required");
151
+ }
152
+ nextConfig.llm = llm;
153
+ delete nextConfig.apiToken;
154
+ delete nextConfig.wallet;
155
+ delete nextConfig.llm.apiKey;
156
+ return { config: nextConfig, secrets };
157
+ }
158
+ function writeSecureStore(path, secrets, password) {
159
+ const secureStorePath = expandHomeDir(path);
160
+ const encrypted = encryptSecretPayload(secrets, password);
161
+ const dir = dirname(secureStorePath);
162
+ if (!existsSync(dir)) {
163
+ mkdirSync(dir, { recursive: true, mode: 448 });
164
+ }
165
+ writeFileSync(secureStorePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
166
+ try {
167
+ chmodSync(secureStorePath, 384);
168
+ } catch {
169
+ }
170
+ return secureStorePath;
171
+ }
172
+ async function ensureLocalSetup(configPath) {
173
+ const config = readConfigFile(configPath);
174
+ const existingSecureStorePath = config.secrets?.secureStorePath ? expandHomeDir(config.secrets.secureStorePath) : null;
175
+ if (existingSecureStorePath && !config.secrets?.bootstrapToken && existsSync(existingSecureStorePath) && !config.apiToken && !config.wallet?.privateKey && !config.llm.apiKey) {
176
+ return;
177
+ }
178
+ const bootstrapPayload = await consumeBootstrapPackage(config);
179
+ const { config: nextConfig, secrets } = await buildLocalSecrets(config, bootstrapPayload);
180
+ const password = await promptPasswordWithConfirmation();
181
+ const secureStorePath = writeSecureStore(
182
+ nextConfig.secrets?.secureStorePath || getDefaultSecureStorePath(nextConfig.agentId),
183
+ secrets,
184
+ password
185
+ );
186
+ nextConfig.secrets = {
187
+ secureStorePath
188
+ };
189
+ writeConfigFile(configPath, nextConfig);
190
+ output.write(`Encrypted local secret store created at ${secureStorePath}
191
+ `);
192
+ }
193
+
194
+ // src/cli.ts
11
195
  var program = new Command();
12
196
  program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.1.0");
13
197
  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) => {
14
198
  writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
15
199
  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");
200
+ console.log("Edit only the public strategy/venue/risk settings in the file.");
201
+ console.log(`Then run: exagent setup --config ${opts.config}`);
202
+ });
203
+ 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) => {
204
+ try {
205
+ await ensureLocalSetup(opts.config);
206
+ console.log("Secure local setup complete.");
207
+ } catch (err) {
208
+ console.error("Failed to complete secure local setup:", err.message);
209
+ process.exit(1);
210
+ }
18
211
  });
19
212
  program.command("run").description("Start the agent").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
20
213
  try {
21
- const config = loadConfig(opts.config);
214
+ await ensureLocalSetup(opts.config);
215
+ const config = await loadConfig(opts.config, {
216
+ getSecretPassword: async () => promptSecretPassword()
217
+ });
22
218
  const runtime = new AgentRuntime(config);
23
219
  await runtime.start();
24
220
  await new Promise(() => {
@@ -40,7 +236,10 @@ program.command("templates").description("List available strategy templates").ac
40
236
  });
41
237
  program.command("status").description("Check agent status").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
42
238
  try {
43
- const config = loadConfig(opts.config);
239
+ await ensureLocalSetup(opts.config);
240
+ const config = await loadConfig(opts.config, {
241
+ getSecretPassword: async () => promptSecretPassword()
242
+ });
44
243
  const res = await fetch(`${config.apiUrl}/v1/agents/${config.agentId}`, {
45
244
  headers: { Authorization: `Bearer ${config.apiToken}` }
46
245
  });
package/dist/index.d.ts CHANGED
@@ -20,6 +20,11 @@ interface RuntimeConfig {
20
20
  strategy: {
21
21
  file?: string;
22
22
  template?: string;
23
+ prompt?: {
24
+ name?: string;
25
+ systemPrompt: string;
26
+ venues?: string[];
27
+ };
23
28
  };
24
29
  trading: {
25
30
  mode: 'live' | 'paper';
@@ -78,7 +83,10 @@ interface RuntimeConfig {
78
83
  json?: boolean;
79
84
  };
80
85
  }
81
- declare function loadConfig(path?: string): RuntimeConfig;
86
+ interface LoadConfigOptions {
87
+ getSecretPassword?: () => Promise<string>;
88
+ }
89
+ declare function loadConfig(path?: string, options?: LoadConfigOptions): Promise<RuntimeConfig>;
82
90
  declare function generateSampleConfig(agentId: string, apiUrl: string): string;
83
91
  declare function writeSampleConfig(agentId: string, apiUrl: string, path?: string): void;
84
92
 
@@ -408,6 +416,11 @@ declare function createLLMAdapter(config: LLMConfig): LLMAdapter;
408
416
  declare function loadStrategy(config: {
409
417
  file?: string;
410
418
  template?: string;
419
+ prompt?: {
420
+ name?: string;
421
+ systemPrompt: string;
422
+ venues?: string[];
423
+ };
411
424
  }): Promise<StrategyFunction>;
412
425
 
413
426
  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-UAP5CTHB.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.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,14 +13,18 @@
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
+ "@exagent/sdk": "workspace:*",
17
22
  "@polymarket/clob-client": "^4.0.0",
18
23
  "commander": "^12.0.0",
19
24
  "ethers": "^5.7.2",
20
25
  "viem": "^2.21.0",
21
26
  "ws": "^8.16.0",
22
- "zod": "^3.22.0",
23
- "@exagent/sdk": "0.2.1"
27
+ "zod": "^3.22.0"
24
28
  },
25
29
  "devDependencies": {
26
30
  "@types/node": "^20.0.0",
@@ -28,9 +32,5 @@
28
32
  "tsup": "^8.0.0",
29
33
  "tsx": "^4.0.0",
30
34
  "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
35
  }
36
- }
36
+ }
package/src/cli.ts CHANGED
@@ -3,6 +3,7 @@ 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';
6
7
 
7
8
  const program = new Command();
8
9
 
@@ -20,8 +21,22 @@ program
20
21
  .action((opts) => {
21
22
  writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
22
23
  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');
24
+ console.log('Edit only the public strategy/venue/risk settings in the file.');
25
+ console.log(`Then run: exagent setup --config ${opts.config}`);
26
+ });
27
+
28
+ program
29
+ .command('setup')
30
+ .description('Run first-time secure local setup for agent secrets')
31
+ .option('--config <path>', 'Config file path', 'agent-config.json')
32
+ .action(async (opts) => {
33
+ try {
34
+ await ensureLocalSetup(opts.config);
35
+ console.log('Secure local setup complete.');
36
+ } catch (err) {
37
+ console.error('Failed to complete secure local setup:', (err as Error).message);
38
+ process.exit(1);
39
+ }
25
40
  });
26
41
 
27
42
  program
@@ -30,7 +45,10 @@ program
30
45
  .option('--config <path>', 'Config file path', 'agent-config.json')
31
46
  .action(async (opts) => {
32
47
  try {
33
- const config = loadConfig(opts.config);
48
+ await ensureLocalSetup(opts.config);
49
+ const config = await loadConfig(opts.config, {
50
+ getSecretPassword: async () => promptSecretPassword(),
51
+ });
34
52
  const runtime = new AgentRuntime(config);
35
53
  await runtime.start();
36
54
 
@@ -62,7 +80,10 @@ program
62
80
  .option('--config <path>', 'Config file path', 'agent-config.json')
63
81
  .action(async (opts) => {
64
82
  try {
65
- const config = loadConfig(opts.config);
83
+ await ensureLocalSetup(opts.config);
84
+ const config = await loadConfig(opts.config, {
85
+ getSecretPassword: async () => promptSecretPassword(),
86
+ });
66
87
  const res = await fetch(`${config.apiUrl}/v1/agents/${config.agentId}`, {
67
88
  headers: { Authorization: `Bearer ${config.apiToken}` },
68
89
  });