@exagent/agent 0.3.1 → 0.3.3
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/chunk-F4TCYYTD.js +6333 -0
- package/dist/chunk-XHYHRJMK.js +6319 -0
- package/dist/chunk-YTZ5MZLP.js +6318 -0
- package/dist/cli.js +73 -15
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/cli.ts +1 -1
- package/src/config.ts +1 -1
- package/src/runtime.ts +18 -2
- package/src/setup.ts +85 -16
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
readConfigFile,
|
|
9
9
|
writeConfigFile,
|
|
10
10
|
writeSampleConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-F4TCYYTD.js";
|
|
12
12
|
|
|
13
13
|
// src/cli.ts
|
|
14
14
|
import { Command } from "commander";
|
|
@@ -87,6 +87,21 @@ function cancelled() {
|
|
|
87
87
|
clack.cancel("Setup cancelled.");
|
|
88
88
|
process.exit(0);
|
|
89
89
|
}
|
|
90
|
+
function isNonInteractive() {
|
|
91
|
+
return process.env.EXAGENT_NONINTERACTIVE === "1" || process.env.EXAGENT_NONINTERACTIVE === "true";
|
|
92
|
+
}
|
|
93
|
+
var LLM_KEY_PREFIXES = {
|
|
94
|
+
openai: "sk-",
|
|
95
|
+
anthropic: "sk-ant-"
|
|
96
|
+
};
|
|
97
|
+
function validateLlmKeyFormat(provider, key) {
|
|
98
|
+
if (!key.trim()) return "API key is required.";
|
|
99
|
+
const expectedPrefix = LLM_KEY_PREFIXES[provider];
|
|
100
|
+
if (expectedPrefix && !key.startsWith(expectedPrefix)) {
|
|
101
|
+
return `${provider} API keys typically start with "${expectedPrefix}". Double-check your key.`;
|
|
102
|
+
}
|
|
103
|
+
if (key.length < 10) return "API key seems too short.";
|
|
104
|
+
}
|
|
90
105
|
async function consumeBootstrapPackage(config) {
|
|
91
106
|
if (!config.secrets?.bootstrapToken) {
|
|
92
107
|
return { apiToken: "" };
|
|
@@ -114,6 +129,22 @@ async function setupWallet(config) {
|
|
|
114
129
|
printDone(`Using existing wallet: ${pc.dim(account.address)}`);
|
|
115
130
|
return config.wallet.privateKey;
|
|
116
131
|
}
|
|
132
|
+
if (isNonInteractive()) {
|
|
133
|
+
const mode = process.env.EXAGENT_WALLET_MODE || "generate";
|
|
134
|
+
if (mode === "import") {
|
|
135
|
+
const key = process.env.EXAGENT_WALLET_KEY;
|
|
136
|
+
if (!key || !/^0x[a-fA-F0-9]{64}$/.test(key)) {
|
|
137
|
+
throw new Error("EXAGENT_WALLET_KEY must be a valid 0x-prefixed 64-char hex private key in non-interactive mode");
|
|
138
|
+
}
|
|
139
|
+
const address3 = privateKeyToAccount(key).address;
|
|
140
|
+
printDone(`Wallet imported: ${pc.dim(address3)}`);
|
|
141
|
+
return key;
|
|
142
|
+
}
|
|
143
|
+
const privateKey2 = generatePrivateKey();
|
|
144
|
+
const address2 = privateKeyToAccount(privateKey2).address;
|
|
145
|
+
printDone(`Wallet created: ${pc.dim(address2)}`);
|
|
146
|
+
return privateKey2;
|
|
147
|
+
}
|
|
117
148
|
const method = await clack.select({
|
|
118
149
|
message: "How would you like to set up your wallet?",
|
|
119
150
|
options: [
|
|
@@ -143,7 +174,18 @@ async function setupWallet(config) {
|
|
|
143
174
|
}
|
|
144
175
|
var LLM_PROVIDERS = ["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"];
|
|
145
176
|
async function setupLlm(config, bootstrapPayload) {
|
|
146
|
-
|
|
177
|
+
const hasRealLlmConfig = !!(bootstrapPayload.llm?.apiKey || config.llm?.apiKey);
|
|
178
|
+
if (isNonInteractive()) {
|
|
179
|
+
const provider2 = process.env.EXAGENT_LLM_PROVIDER || bootstrapPayload.llm?.provider || (hasRealLlmConfig ? config.llm?.provider : void 0);
|
|
180
|
+
const model2 = process.env.EXAGENT_LLM_MODEL || bootstrapPayload.llm?.model || (hasRealLlmConfig ? config.llm?.model : void 0);
|
|
181
|
+
const apiKey2 = process.env.EXAGENT_LLM_KEY || bootstrapPayload.llm?.apiKey || config.llm?.apiKey;
|
|
182
|
+
if (!provider2) throw new Error("EXAGENT_LLM_PROVIDER required in non-interactive mode");
|
|
183
|
+
if (!model2) throw new Error("EXAGENT_LLM_MODEL required in non-interactive mode");
|
|
184
|
+
if (!apiKey2) throw new Error("EXAGENT_LLM_KEY required in non-interactive mode");
|
|
185
|
+
printDone("LLM configured");
|
|
186
|
+
return { provider: provider2, model: model2, apiKey: apiKey2 };
|
|
187
|
+
}
|
|
188
|
+
let provider = bootstrapPayload.llm?.provider || (hasRealLlmConfig ? config.llm?.provider : void 0);
|
|
147
189
|
if (provider) {
|
|
148
190
|
printInfo(`Provider: ${pc.cyan(provider)} ${pc.dim("(from dashboard)")}`);
|
|
149
191
|
} else {
|
|
@@ -154,7 +196,7 @@ async function setupLlm(config, bootstrapPayload) {
|
|
|
154
196
|
if (clack.isCancel(selected)) cancelled();
|
|
155
197
|
provider = selected;
|
|
156
198
|
}
|
|
157
|
-
let model =
|
|
199
|
+
let model = bootstrapPayload.llm?.model || (hasRealLlmConfig ? config.llm?.model : void 0);
|
|
158
200
|
if (model) {
|
|
159
201
|
printInfo(`Model: ${pc.cyan(model)} ${pc.dim("(from dashboard)")}`);
|
|
160
202
|
} else {
|
|
@@ -185,9 +227,7 @@ async function setupLlm(config, bootstrapPayload) {
|
|
|
185
227
|
if (!apiKey) {
|
|
186
228
|
const entered = await clack.password({
|
|
187
229
|
message: "LLM API key:",
|
|
188
|
-
validate: (val) =>
|
|
189
|
-
if (!val.trim()) return "API key is required.";
|
|
190
|
-
}
|
|
230
|
+
validate: (val) => validateLlmKeyFormat(provider, val)
|
|
191
231
|
});
|
|
192
232
|
if (clack.isCancel(entered)) cancelled();
|
|
193
233
|
apiKey = entered;
|
|
@@ -196,6 +236,13 @@ async function setupLlm(config, bootstrapPayload) {
|
|
|
196
236
|
return { provider, model, apiKey };
|
|
197
237
|
}
|
|
198
238
|
async function setupEncryption() {
|
|
239
|
+
if (isNonInteractive()) {
|
|
240
|
+
const password3 = process.env.EXAGENT_PASSWORD;
|
|
241
|
+
if (!password3 || password3.length < 12) {
|
|
242
|
+
throw new Error("EXAGENT_PASSWORD must be at least 12 characters in non-interactive mode");
|
|
243
|
+
}
|
|
244
|
+
return password3;
|
|
245
|
+
}
|
|
199
246
|
printInfo(`Secrets encrypted with ${pc.cyan("AES-256-GCM")} (${pc.cyan("scrypt")} KDF)`);
|
|
200
247
|
printInfo("The password never leaves this machine.");
|
|
201
248
|
console.log();
|
|
@@ -230,6 +277,11 @@ function writeSecureStore(path, secrets, password2) {
|
|
|
230
277
|
return secureStorePath;
|
|
231
278
|
}
|
|
232
279
|
async function promptSecretPassword(question = "Device password:") {
|
|
280
|
+
if (isNonInteractive()) {
|
|
281
|
+
const password3 = process.env.EXAGENT_PASSWORD || process.env.EXAGENT_SECRET_PASSWORD;
|
|
282
|
+
if (!password3) throw new Error("EXAGENT_PASSWORD required in non-interactive mode");
|
|
283
|
+
return password3;
|
|
284
|
+
}
|
|
233
285
|
const password2 = await clack.password({ message: question });
|
|
234
286
|
if (clack.isCancel(password2)) cancelled();
|
|
235
287
|
return password2;
|
|
@@ -264,14 +316,20 @@ async function ensureLocalSetup(configPath) {
|
|
|
264
316
|
llmApiKey: llm.apiKey
|
|
265
317
|
};
|
|
266
318
|
if (!secrets.apiToken) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
319
|
+
if (isNonInteractive()) {
|
|
320
|
+
const token = process.env.EXAGENT_API_TOKEN;
|
|
321
|
+
if (!token) throw new Error("EXAGENT_API_TOKEN required in non-interactive mode (no relay token from bootstrap)");
|
|
322
|
+
secrets.apiToken = token;
|
|
323
|
+
} else {
|
|
324
|
+
const token = await clack.password({
|
|
325
|
+
message: "Agent relay token:",
|
|
326
|
+
validate: (val) => {
|
|
327
|
+
if (!val.trim()) return "Relay token is required.";
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
if (clack.isCancel(token)) cancelled();
|
|
331
|
+
secrets.apiToken = token;
|
|
332
|
+
}
|
|
275
333
|
}
|
|
276
334
|
const nextConfig = structuredClone(config);
|
|
277
335
|
nextConfig.llm = {
|
|
@@ -301,7 +359,7 @@ async function ensureLocalSetup(configPath) {
|
|
|
301
359
|
|
|
302
360
|
// src/cli.ts
|
|
303
361
|
var program = new Command();
|
|
304
|
-
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.
|
|
362
|
+
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.3");
|
|
305
363
|
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
364
|
printBanner();
|
|
307
365
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exagent/agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@clack/prompts": "^1.1.0",
|
|
22
|
-
"@exagent/sdk": "
|
|
22
|
+
"@exagent/sdk": "^0.2.1",
|
|
23
23
|
"@polymarket/clob-client": "^4.0.0",
|
|
24
24
|
"boxen": "^8.0.1",
|
|
25
25
|
"commander": "^12.0.0",
|
package/src/cli.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -258,7 +258,7 @@ const secureStoreSchema = z.object({
|
|
|
258
258
|
|
|
259
259
|
export type RuntimeConfigFile = z.infer<typeof configFileSchema>;
|
|
260
260
|
|
|
261
|
-
const DEFAULT_SCRYPT_COST =
|
|
261
|
+
const DEFAULT_SCRYPT_COST = 16384;
|
|
262
262
|
const DEFAULT_SCRYPT_BLOCK_SIZE = 8;
|
|
263
263
|
const DEFAULT_SCRYPT_PARALLELIZATION = 1;
|
|
264
264
|
|
package/src/runtime.ts
CHANGED
|
@@ -58,6 +58,21 @@ try { SDK_VERSION = _require('../package.json').version; } catch {}
|
|
|
58
58
|
/** Number of consecutive cycle failures before switching to idle */
|
|
59
59
|
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
60
60
|
|
|
61
|
+
function getCycleErrorHint(category: string, msg: string): string | null {
|
|
62
|
+
const lower = msg.toLowerCase();
|
|
63
|
+
if (category === 'auth' || lower.includes('401') || lower.includes('unauthorized') || lower.includes('invalid api key') || lower.includes('authentication'))
|
|
64
|
+
return 'Check your LLM API key — update via npx exagent setup';
|
|
65
|
+
if (category === 'network' || lower.includes('econnrefused') || lower.includes('enotfound') || lower.includes('fetch failed') || lower.includes('timeout'))
|
|
66
|
+
return 'Network connectivity issue — check your internet connection';
|
|
67
|
+
if (category === 'venue' || lower.includes('hyperliquid') || lower.includes('polymarket') || lower.includes('venue'))
|
|
68
|
+
return 'Venue API error — the venue may be experiencing issues';
|
|
69
|
+
if (category === 'strategy' || lower.includes('invalid') && lower.includes('signal') || lower.includes('strategy'))
|
|
70
|
+
return 'Strategy returned invalid output — check your strategy file';
|
|
71
|
+
if (lower.includes('rate limit') || lower.includes('429'))
|
|
72
|
+
return 'Rate limited — consider increasing tradingIntervalMs';
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
export class AgentRuntime {
|
|
62
77
|
private config: RuntimeConfig;
|
|
63
78
|
private relay: RelayClient;
|
|
@@ -809,12 +824,13 @@ export class AgentRuntime {
|
|
|
809
824
|
timings.totalMs = Date.now() - cycleStart;
|
|
810
825
|
|
|
811
826
|
const categorized = this.diagnostics.recordError(err as Error);
|
|
812
|
-
|
|
827
|
+
const hint = getCycleErrorHint(categorized.category, errMsg);
|
|
828
|
+
log.error('cycle', `Cycle error: ${errMsg}${hint ? ` — ${hint}` : ''}`, {
|
|
813
829
|
cycle: this.cycleCount,
|
|
814
830
|
totalMs: timings.totalMs,
|
|
815
831
|
category: categorized.category,
|
|
816
832
|
});
|
|
817
|
-
this.signal.reportError('Cycle Error', errMsg);
|
|
833
|
+
this.signal.reportError('Cycle Error', `${errMsg}${hint ? ` — ${hint}` : ''}`);
|
|
818
834
|
|
|
819
835
|
this.diagnostics.recordCycle({
|
|
820
836
|
cycleNumber: this.cycleCount,
|
package/src/setup.ts
CHANGED
|
@@ -33,6 +33,24 @@ function cancelled(): never {
|
|
|
33
33
|
process.exit(0);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function isNonInteractive(): boolean {
|
|
37
|
+
return process.env.EXAGENT_NONINTERACTIVE === '1' || process.env.EXAGENT_NONINTERACTIVE === 'true';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const LLM_KEY_PREFIXES: Record<string, string> = {
|
|
41
|
+
openai: 'sk-',
|
|
42
|
+
anthropic: 'sk-ant-',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function validateLlmKeyFormat(provider: string, key: string): string | undefined {
|
|
46
|
+
if (!key.trim()) return 'API key is required.';
|
|
47
|
+
const expectedPrefix = LLM_KEY_PREFIXES[provider];
|
|
48
|
+
if (expectedPrefix && !key.startsWith(expectedPrefix)) {
|
|
49
|
+
return `${provider} API keys typically start with "${expectedPrefix}". Double-check your key.`;
|
|
50
|
+
}
|
|
51
|
+
if (key.length < 10) return 'API key seems too short.';
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
// ---------------------------------------------------------------------------
|
|
37
55
|
// Step 1: Bootstrap
|
|
38
56
|
// ---------------------------------------------------------------------------
|
|
@@ -74,6 +92,24 @@ async function setupWallet(config: RuntimeConfigFile): Promise<string> {
|
|
|
74
92
|
return config.wallet.privateKey;
|
|
75
93
|
}
|
|
76
94
|
|
|
95
|
+
// Non-interactive: env var wallet
|
|
96
|
+
if (isNonInteractive()) {
|
|
97
|
+
const mode = process.env.EXAGENT_WALLET_MODE || 'generate';
|
|
98
|
+
if (mode === 'import') {
|
|
99
|
+
const key = process.env.EXAGENT_WALLET_KEY;
|
|
100
|
+
if (!key || !/^0x[a-fA-F0-9]{64}$/.test(key)) {
|
|
101
|
+
throw new Error('EXAGENT_WALLET_KEY must be a valid 0x-prefixed 64-char hex private key in non-interactive mode');
|
|
102
|
+
}
|
|
103
|
+
const address = privateKeyToAccount(key as `0x${string}`).address;
|
|
104
|
+
printDone(`Wallet imported: ${pc.dim(address)}`);
|
|
105
|
+
return key;
|
|
106
|
+
}
|
|
107
|
+
const privateKey = generatePrivateKey();
|
|
108
|
+
const address = privateKeyToAccount(privateKey).address;
|
|
109
|
+
printDone(`Wallet created: ${pc.dim(address)}`);
|
|
110
|
+
return privateKey;
|
|
111
|
+
}
|
|
112
|
+
|
|
77
113
|
const method = await clack.select({
|
|
78
114
|
message: 'How would you like to set up your wallet?',
|
|
79
115
|
options: [
|
|
@@ -116,8 +152,24 @@ async function setupLlm(
|
|
|
116
152
|
config: RuntimeConfigFile,
|
|
117
153
|
bootstrapPayload: BootstrapPayload,
|
|
118
154
|
): Promise<{ provider: string; model: string; apiKey: string }> {
|
|
119
|
-
//
|
|
120
|
-
|
|
155
|
+
// D8: If bootstrap has no LLM payload AND config has no real apiKey,
|
|
156
|
+
// the config's provider/model are just wizard defaults — don't trust them.
|
|
157
|
+
const hasRealLlmConfig = !!(bootstrapPayload.llm?.apiKey || config.llm?.apiKey);
|
|
158
|
+
|
|
159
|
+
// Non-interactive mode
|
|
160
|
+
if (isNonInteractive()) {
|
|
161
|
+
const provider = process.env.EXAGENT_LLM_PROVIDER || bootstrapPayload.llm?.provider || (hasRealLlmConfig ? config.llm?.provider : undefined);
|
|
162
|
+
const model = process.env.EXAGENT_LLM_MODEL || bootstrapPayload.llm?.model || (hasRealLlmConfig ? config.llm?.model : undefined);
|
|
163
|
+
const apiKey = process.env.EXAGENT_LLM_KEY || bootstrapPayload.llm?.apiKey || config.llm?.apiKey;
|
|
164
|
+
if (!provider) throw new Error('EXAGENT_LLM_PROVIDER required in non-interactive mode');
|
|
165
|
+
if (!model) throw new Error('EXAGENT_LLM_MODEL required in non-interactive mode');
|
|
166
|
+
if (!apiKey) throw new Error('EXAGENT_LLM_KEY required in non-interactive mode');
|
|
167
|
+
printDone('LLM configured');
|
|
168
|
+
return { provider, model, apiKey };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Provider — only trust config value if we have a real LLM config
|
|
172
|
+
let provider = bootstrapPayload.llm?.provider || (hasRealLlmConfig ? config.llm?.provider : undefined);
|
|
121
173
|
if (provider) {
|
|
122
174
|
printInfo(`Provider: ${pc.cyan(provider)} ${pc.dim('(from dashboard)')}`);
|
|
123
175
|
} else {
|
|
@@ -129,8 +181,8 @@ async function setupLlm(
|
|
|
129
181
|
provider = selected;
|
|
130
182
|
}
|
|
131
183
|
|
|
132
|
-
// Model
|
|
133
|
-
let model =
|
|
184
|
+
// Model — only trust config value if we have a real LLM config
|
|
185
|
+
let model = bootstrapPayload.llm?.model || (hasRealLlmConfig ? config.llm?.model : undefined);
|
|
134
186
|
if (model) {
|
|
135
187
|
printInfo(`Model: ${pc.cyan(model)} ${pc.dim('(from dashboard)')}`);
|
|
136
188
|
} else {
|
|
@@ -163,9 +215,7 @@ async function setupLlm(
|
|
|
163
215
|
if (!apiKey) {
|
|
164
216
|
const entered = await clack.password({
|
|
165
217
|
message: 'LLM API key:',
|
|
166
|
-
validate: (val) =>
|
|
167
|
-
if (!val.trim()) return 'API key is required.';
|
|
168
|
-
},
|
|
218
|
+
validate: (val) => validateLlmKeyFormat(provider, val),
|
|
169
219
|
});
|
|
170
220
|
if (clack.isCancel(entered)) cancelled();
|
|
171
221
|
apiKey = entered;
|
|
@@ -180,6 +230,15 @@ async function setupLlm(
|
|
|
180
230
|
// ---------------------------------------------------------------------------
|
|
181
231
|
|
|
182
232
|
async function setupEncryption(): Promise<string> {
|
|
233
|
+
// Non-interactive mode
|
|
234
|
+
if (isNonInteractive()) {
|
|
235
|
+
const password = process.env.EXAGENT_PASSWORD;
|
|
236
|
+
if (!password || password.length < 12) {
|
|
237
|
+
throw new Error('EXAGENT_PASSWORD must be at least 12 characters in non-interactive mode');
|
|
238
|
+
}
|
|
239
|
+
return password;
|
|
240
|
+
}
|
|
241
|
+
|
|
183
242
|
printInfo(`Secrets encrypted with ${pc.cyan('AES-256-GCM')} (${pc.cyan('scrypt')} KDF)`);
|
|
184
243
|
printInfo('The password never leaves this machine.');
|
|
185
244
|
console.log();
|
|
@@ -228,6 +287,11 @@ function writeSecureStore(path: string, secrets: LocalSecretPayload, password: s
|
|
|
228
287
|
// ---------------------------------------------------------------------------
|
|
229
288
|
|
|
230
289
|
export async function promptSecretPassword(question: string = 'Device password:'): Promise<string> {
|
|
290
|
+
if (isNonInteractive()) {
|
|
291
|
+
const password = process.env.EXAGENT_PASSWORD || process.env.EXAGENT_SECRET_PASSWORD;
|
|
292
|
+
if (!password) throw new Error('EXAGENT_PASSWORD required in non-interactive mode');
|
|
293
|
+
return password;
|
|
294
|
+
}
|
|
231
295
|
const password = await clack.password({ message: question });
|
|
232
296
|
if (clack.isCancel(password)) cancelled();
|
|
233
297
|
return password;
|
|
@@ -287,15 +351,20 @@ export async function ensureLocalSetup(configPath: string): Promise<void> {
|
|
|
287
351
|
};
|
|
288
352
|
|
|
289
353
|
if (!secrets.apiToken) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
354
|
+
if (isNonInteractive()) {
|
|
355
|
+
const token = process.env.EXAGENT_API_TOKEN;
|
|
356
|
+
if (!token) throw new Error('EXAGENT_API_TOKEN required in non-interactive mode (no relay token from bootstrap)');
|
|
357
|
+
secrets.apiToken = token;
|
|
358
|
+
} else {
|
|
359
|
+
const token = await clack.password({
|
|
360
|
+
message: 'Agent relay token:',
|
|
361
|
+
validate: (val) => {
|
|
362
|
+
if (!val.trim()) return 'Relay token is required.';
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
if (clack.isCancel(token)) cancelled();
|
|
366
|
+
secrets.apiToken = token;
|
|
367
|
+
}
|
|
299
368
|
}
|
|
300
369
|
|
|
301
370
|
const nextConfig = structuredClone(config);
|