@elisym/cli 0.3.2 → 0.5.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/README.md +70 -33
- package/dist/index.js +781 -616
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-deprecation
|
|
2
|
-
import { readFileSync,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { isEncrypted, parseConfig, encryptSecret } from '@elisym/sdk/node';
|
|
2
|
+
import { readFileSync, readdirSync, statSync, renameSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve, basename, relative, sep } from 'node:path';
|
|
4
|
+
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, DEFAULTS, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet } from '@elisym/sdk';
|
|
5
|
+
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYaml, writeSecrets, listAgents, loadAgent, agentPaths, readMediaCache, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
7
6
|
import { isAddress, createSolanaRpc, address } from '@solana/kit';
|
|
8
|
-
import {
|
|
7
|
+
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
|
|
8
|
+
import YAML from 'yaml';
|
|
9
9
|
import { Command } from 'commander';
|
|
10
|
+
import { isEncrypted } from '@elisym/sdk/node';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { lookup } from 'node:dns/promises';
|
|
13
|
+
import { Socket } from 'node:net';
|
|
14
|
+
import pino from 'pino';
|
|
10
15
|
import pLimit from 'p-limit';
|
|
11
|
-
import
|
|
12
|
-
import { spawn } from 'node:child_process';
|
|
13
|
-
import { StringDecoder } from 'node:string_decoder';
|
|
16
|
+
import { parseSkillMd, validateSkillFrontmatter, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
14
17
|
import { fileURLToPath } from 'node:url';
|
|
15
18
|
|
|
16
19
|
var __defProp = Object.defineProperty;
|
|
@@ -22,69 +25,6 @@ var __export = (target, all) => {
|
|
|
22
25
|
for (var name in all)
|
|
23
26
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
27
|
};
|
|
25
|
-
function agentDir(name) {
|
|
26
|
-
return join(AGENTS_ROOT, name);
|
|
27
|
-
}
|
|
28
|
-
function configPath(name) {
|
|
29
|
-
return join(AGENTS_ROOT, name, "config.json");
|
|
30
|
-
}
|
|
31
|
-
function loadConfig(name, passphrase) {
|
|
32
|
-
validateAgentName(name);
|
|
33
|
-
const path = configPath(name);
|
|
34
|
-
const raw = readFileSync(path, "utf-8");
|
|
35
|
-
return parseConfig(raw, passphrase);
|
|
36
|
-
}
|
|
37
|
-
function saveConfig(config) {
|
|
38
|
-
validateAgentName(config.identity.name);
|
|
39
|
-
const dir = agentDir(config.identity.name);
|
|
40
|
-
mkdirSync(dir, { recursive: true });
|
|
41
|
-
writeFileSync(join(dir, "config.json"), serializeConfig(config), { mode: 384 });
|
|
42
|
-
}
|
|
43
|
-
function listAgents() {
|
|
44
|
-
try {
|
|
45
|
-
return readdirSync(AGENTS_ROOT).filter((name) => {
|
|
46
|
-
try {
|
|
47
|
-
statSync(join(AGENTS_ROOT, name, "config.json"));
|
|
48
|
-
return true;
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
} catch {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function deleteAgent(name) {
|
|
58
|
-
validateAgentName(name);
|
|
59
|
-
const dir = agentDir(name);
|
|
60
|
-
for (const file of ["config.json", "jobs.json"]) {
|
|
61
|
-
try {
|
|
62
|
-
const filePath = join(dir, file);
|
|
63
|
-
const size = statSync(filePath).size;
|
|
64
|
-
writeFileSync(filePath, Buffer.alloc(size, 0));
|
|
65
|
-
} catch {
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
for (const entry of readdirSync(dir)) {
|
|
70
|
-
if (entry.startsWith("jobs.json.corrupt.")) {
|
|
71
|
-
try {
|
|
72
|
-
const fp = join(dir, entry);
|
|
73
|
-
writeFileSync(fp, Buffer.alloc(statSync(fp).size, 0));
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
}
|
|
80
|
-
rmSync(dir, { recursive: true, force: true });
|
|
81
|
-
}
|
|
82
|
-
var AGENTS_ROOT;
|
|
83
|
-
var init_config = __esm({
|
|
84
|
-
"src/config.ts"() {
|
|
85
|
-
AGENTS_ROOT = join(homedir(), ".elisym", "agents");
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
28
|
|
|
89
29
|
// src/commands/init.ts
|
|
90
30
|
var init_exports = {};
|
|
@@ -92,20 +32,6 @@ __export(init_exports, {
|
|
|
92
32
|
cmdInit: () => cmdInit,
|
|
93
33
|
fetchModels: () => fetchModels
|
|
94
34
|
});
|
|
95
|
-
async function resolveImage(input, identity, media) {
|
|
96
|
-
if (!input) {
|
|
97
|
-
return "";
|
|
98
|
-
}
|
|
99
|
-
if (existsSync(input)) {
|
|
100
|
-
console.log(` Uploading ${basename(input)}...`);
|
|
101
|
-
const data = readFileSync(input);
|
|
102
|
-
const blob = new Blob([data]);
|
|
103
|
-
const url = await media.upload(identity, blob, basename(input));
|
|
104
|
-
console.log(` Uploaded: ${url}`);
|
|
105
|
-
return url;
|
|
106
|
-
}
|
|
107
|
-
return input;
|
|
108
|
-
}
|
|
109
35
|
async function fetchModels(provider, apiKey) {
|
|
110
36
|
try {
|
|
111
37
|
if (provider === "anthropic") {
|
|
@@ -138,31 +64,30 @@ async function fetchModels(provider, apiKey) {
|
|
|
138
64
|
return FALLBACK_MODELS[provider] ?? ["gpt-4o"];
|
|
139
65
|
}
|
|
140
66
|
}
|
|
141
|
-
|
|
67
|
+
function pickTarget(options) {
|
|
68
|
+
return options.local ? "project" : "home";
|
|
69
|
+
}
|
|
70
|
+
async function cmdInit(nameArg, options = {}) {
|
|
142
71
|
const { default: inquirer } = await import('inquirer');
|
|
72
|
+
const cwd = process.cwd();
|
|
143
73
|
console.log("\n elisym agent setup\n");
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
]);
|
|
159
|
-
const existing = listAgents();
|
|
160
|
-
if (existing.includes(name)) {
|
|
74
|
+
let template;
|
|
75
|
+
if (options.config) {
|
|
76
|
+
const configPath = resolve(cwd, options.config);
|
|
77
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
78
|
+
template = ElisymYamlSchema.parse(YAML.parse(raw) ?? {});
|
|
79
|
+
console.log(` Loaded template from ${configPath}
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
const agentName = await resolveAgentName(nameArg, inquirer);
|
|
83
|
+
const target = pickTarget(options);
|
|
84
|
+
const sameLocation = target === "home" ? resolveInHome(agentName) : resolveInProject(agentName, cwd);
|
|
85
|
+
if (sameLocation) {
|
|
161
86
|
const { overwrite } = await inquirer.prompt([
|
|
162
87
|
{
|
|
163
88
|
type: "confirm",
|
|
164
89
|
name: "overwrite",
|
|
165
|
-
message: `Agent "${
|
|
90
|
+
message: `Agent "${agentName}" already exists at ${sameLocation}. Overwrite secrets?`,
|
|
166
91
|
default: false
|
|
167
92
|
}
|
|
168
93
|
]);
|
|
@@ -170,7 +95,133 @@ async function cmdInit() {
|
|
|
170
95
|
console.log("Aborted.");
|
|
171
96
|
return;
|
|
172
97
|
}
|
|
98
|
+
} else if (target === "project" && resolveInHome(agentName)) {
|
|
99
|
+
const { shadow } = await inquirer.prompt([
|
|
100
|
+
{
|
|
101
|
+
type: "confirm",
|
|
102
|
+
name: "shadow",
|
|
103
|
+
message: `A global agent "${agentName}" exists in ~/.elisym/${agentName}/. Create a project-local shadow?`,
|
|
104
|
+
default: true
|
|
105
|
+
}
|
|
106
|
+
]);
|
|
107
|
+
if (!shadow) {
|
|
108
|
+
console.log("Aborted.");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
} else if (target === "home" && resolveInProject(agentName, cwd)) {
|
|
112
|
+
const { proceed } = await inquirer.prompt([
|
|
113
|
+
{
|
|
114
|
+
type: "confirm",
|
|
115
|
+
name: "proceed",
|
|
116
|
+
message: `A project-local agent "${agentName}" exists. Create a global agent with the same name?`,
|
|
117
|
+
default: true
|
|
118
|
+
}
|
|
119
|
+
]);
|
|
120
|
+
if (!proceed) {
|
|
121
|
+
console.log("Aborted.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let yaml;
|
|
126
|
+
let promptedApiKey;
|
|
127
|
+
if (template) {
|
|
128
|
+
yaml = template;
|
|
129
|
+
} else {
|
|
130
|
+
const result = await promptYaml(inquirer);
|
|
131
|
+
yaml = result.yaml;
|
|
132
|
+
promptedApiKey = result.apiKey;
|
|
133
|
+
}
|
|
134
|
+
let llmApiKey;
|
|
135
|
+
if (yaml.llm) {
|
|
136
|
+
const envKey = yaml.llm.provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
|
|
137
|
+
if (envKey) {
|
|
138
|
+
llmApiKey = envKey;
|
|
139
|
+
console.log(
|
|
140
|
+
` Using ${yaml.llm.provider === "anthropic" ? "ANTHROPIC" : "OPENAI"}_API_KEY from environment.`
|
|
141
|
+
);
|
|
142
|
+
} else if (promptedApiKey) {
|
|
143
|
+
llmApiKey = promptedApiKey;
|
|
144
|
+
} else {
|
|
145
|
+
const { apiKey } = await inquirer.prompt([
|
|
146
|
+
{
|
|
147
|
+
type: "password",
|
|
148
|
+
name: "apiKey",
|
|
149
|
+
message: `${yaml.llm.provider === "anthropic" ? "Anthropic" : "OpenAI"} API key:`,
|
|
150
|
+
mask: "*"
|
|
151
|
+
}
|
|
152
|
+
]);
|
|
153
|
+
llmApiKey = apiKey || void 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const { passphrase } = await inquirer.prompt([
|
|
157
|
+
{
|
|
158
|
+
type: "password",
|
|
159
|
+
name: "passphrase",
|
|
160
|
+
message: "Passphrase to encrypt secrets (leave empty to skip):",
|
|
161
|
+
mask: "*"
|
|
162
|
+
}
|
|
163
|
+
]);
|
|
164
|
+
if (passphrase) {
|
|
165
|
+
const { confirmPassphrase } = await inquirer.prompt([
|
|
166
|
+
{
|
|
167
|
+
type: "password",
|
|
168
|
+
name: "confirmPassphrase",
|
|
169
|
+
message: "Confirm passphrase:",
|
|
170
|
+
mask: "*",
|
|
171
|
+
validate: (value) => value === passphrase || "Passphrases do not match"
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
const nostrSecretBytes = generateSecretKey();
|
|
176
|
+
const nostrPubkey = getPublicKey(nostrSecretBytes);
|
|
177
|
+
const nostrSecretHex = Buffer.from(nostrSecretBytes).toString("hex");
|
|
178
|
+
const created = await createAgentDir({ target, name: agentName, cwd });
|
|
179
|
+
await writeYaml(created.dir, yaml);
|
|
180
|
+
await writeSecrets(
|
|
181
|
+
created.dir,
|
|
182
|
+
{
|
|
183
|
+
nostr_secret_key: nostrSecretHex,
|
|
184
|
+
llm_api_key: llmApiKey
|
|
185
|
+
},
|
|
186
|
+
passphrase || void 0
|
|
187
|
+
);
|
|
188
|
+
const npub = nip19.npubEncode(nostrPubkey);
|
|
189
|
+
console.log(`
|
|
190
|
+
Agent "${agentName}" created (${target}).`);
|
|
191
|
+
console.log(` Location: ${created.dir}`);
|
|
192
|
+
console.log(` Nostr: ${npub}`);
|
|
193
|
+
if (yaml.payments[0]?.address) {
|
|
194
|
+
console.log(` Solana: ${yaml.payments[0].address}`);
|
|
195
|
+
}
|
|
196
|
+
if (passphrase) {
|
|
197
|
+
console.log(" Secrets encrypted with your passphrase.");
|
|
173
198
|
}
|
|
199
|
+
console.log();
|
|
200
|
+
}
|
|
201
|
+
async function resolveAgentName(nameArg, inquirer) {
|
|
202
|
+
if (nameArg) {
|
|
203
|
+
validateAgentName(nameArg);
|
|
204
|
+
return nameArg;
|
|
205
|
+
}
|
|
206
|
+
const { inputName } = await inquirer.prompt([
|
|
207
|
+
{
|
|
208
|
+
type: "input",
|
|
209
|
+
name: "inputName",
|
|
210
|
+
message: "Agent name:",
|
|
211
|
+
validate: (value) => {
|
|
212
|
+
try {
|
|
213
|
+
validateAgentName(value);
|
|
214
|
+
return true;
|
|
215
|
+
} catch (e) {
|
|
216
|
+
return e.message;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
]);
|
|
221
|
+
validateAgentName(inputName);
|
|
222
|
+
return inputName;
|
|
223
|
+
}
|
|
224
|
+
async function promptYaml(inquirer) {
|
|
174
225
|
const { description } = await inquirer.prompt([
|
|
175
226
|
{
|
|
176
227
|
type: "input",
|
|
@@ -179,19 +230,27 @@ async function cmdInit() {
|
|
|
179
230
|
default: "An elisym AI agent"
|
|
180
231
|
}
|
|
181
232
|
]);
|
|
182
|
-
const {
|
|
233
|
+
const { displayName } = await inquirer.prompt([
|
|
234
|
+
{
|
|
235
|
+
type: "input",
|
|
236
|
+
name: "displayName",
|
|
237
|
+
message: "Display name (optional, for UI):",
|
|
238
|
+
default: ""
|
|
239
|
+
}
|
|
240
|
+
]);
|
|
241
|
+
const { picture } = await inquirer.prompt([
|
|
183
242
|
{
|
|
184
243
|
type: "input",
|
|
185
|
-
name: "
|
|
186
|
-
message: "Avatar
|
|
244
|
+
name: "picture",
|
|
245
|
+
message: "Avatar file (relative to agent dir, e.g. ./avatar.png) or URL:",
|
|
187
246
|
default: ""
|
|
188
247
|
}
|
|
189
248
|
]);
|
|
190
|
-
const {
|
|
249
|
+
const { banner } = await inquirer.prompt([
|
|
191
250
|
{
|
|
192
251
|
type: "input",
|
|
193
|
-
name: "
|
|
194
|
-
message: "Banner
|
|
252
|
+
name: "banner",
|
|
253
|
+
message: "Banner file (relative to agent dir, e.g. ./header.png) or URL:",
|
|
195
254
|
default: ""
|
|
196
255
|
}
|
|
197
256
|
]);
|
|
@@ -201,23 +260,14 @@ async function cmdInit() {
|
|
|
201
260
|
name: "solanaAddress",
|
|
202
261
|
message: "Solana address for receiving payments (leave empty to skip):",
|
|
203
262
|
default: "",
|
|
204
|
-
validate: (
|
|
205
|
-
if (!
|
|
263
|
+
validate: (value) => {
|
|
264
|
+
if (!value) {
|
|
206
265
|
return true;
|
|
207
266
|
}
|
|
208
|
-
return isAddress(
|
|
267
|
+
return isAddress(value) || "Invalid Solana address";
|
|
209
268
|
}
|
|
210
269
|
}
|
|
211
270
|
]);
|
|
212
|
-
const { network } = await inquirer.prompt([
|
|
213
|
-
{
|
|
214
|
-
type: "list",
|
|
215
|
-
name: "network",
|
|
216
|
-
message: "Solana network:",
|
|
217
|
-
choices: [{ name: "devnet", value: "devnet" }],
|
|
218
|
-
default: "devnet"
|
|
219
|
-
}
|
|
220
|
-
]);
|
|
221
271
|
const { llmProvider } = await inquirer.prompt([
|
|
222
272
|
{
|
|
223
273
|
type: "list",
|
|
@@ -229,16 +279,21 @@ async function cmdInit() {
|
|
|
229
279
|
]
|
|
230
280
|
}
|
|
231
281
|
]);
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
282
|
+
const envKey = llmProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
|
|
283
|
+
let apiKey = envKey;
|
|
284
|
+
if (!apiKey) {
|
|
285
|
+
const { promptedKey } = await inquirer.prompt([
|
|
286
|
+
{
|
|
287
|
+
type: "password",
|
|
288
|
+
name: "promptedKey",
|
|
289
|
+
message: `${llmProvider === "anthropic" ? "Anthropic" : "OpenAI"} API key:`,
|
|
290
|
+
mask: "*"
|
|
291
|
+
}
|
|
292
|
+
]);
|
|
293
|
+
apiKey = promptedKey || void 0;
|
|
294
|
+
}
|
|
240
295
|
console.log(" Fetching available models...");
|
|
241
|
-
const models = await fetchModels(llmProvider, apiKey);
|
|
296
|
+
const models = await fetchModels(llmProvider, apiKey ?? "");
|
|
242
297
|
const { model } = await inquirer.prompt([
|
|
243
298
|
{
|
|
244
299
|
type: "list",
|
|
@@ -255,77 +310,21 @@ async function cmdInit() {
|
|
|
255
310
|
default: 4096
|
|
256
311
|
}
|
|
257
312
|
]);
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
mask: "*"
|
|
264
|
-
}
|
|
265
|
-
]);
|
|
266
|
-
if (passphrase) {
|
|
267
|
-
const { confirmPassphrase } = await inquirer.prompt([
|
|
268
|
-
{
|
|
269
|
-
type: "password",
|
|
270
|
-
name: "confirmPassphrase",
|
|
271
|
-
message: "Confirm passphrase:",
|
|
272
|
-
mask: "*",
|
|
273
|
-
validate: (v) => v === passphrase ? true : "Passphrases do not match"
|
|
274
|
-
}
|
|
275
|
-
]);
|
|
276
|
-
}
|
|
277
|
-
const nostrSecretKey = generateSecretKey();
|
|
278
|
-
const nostrPubkey = getPublicKey(nostrSecretKey);
|
|
279
|
-
const nostrSecretHex = Buffer.from(nostrSecretKey).toString("hex");
|
|
280
|
-
const identity = ElisymIdentity.fromHex(nostrSecretHex);
|
|
281
|
-
const media = new MediaService();
|
|
282
|
-
let picture = "";
|
|
283
|
-
let banner = "";
|
|
284
|
-
if (pictureInput || bannerInput) {
|
|
285
|
-
console.log();
|
|
286
|
-
if (pictureInput) {
|
|
287
|
-
picture = await resolveImage(pictureInput, identity, media);
|
|
288
|
-
}
|
|
289
|
-
if (bannerInput) {
|
|
290
|
-
banner = await resolveImage(bannerInput, identity, media);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
const protect = (secret) => passphrase ? encryptSecret(secret, passphrase) : secret;
|
|
294
|
-
const config = {
|
|
295
|
-
identity: {
|
|
296
|
-
secret_key: protect(nostrSecretHex),
|
|
297
|
-
name,
|
|
298
|
-
description,
|
|
299
|
-
picture: picture || void 0,
|
|
300
|
-
banner: banner || void 0
|
|
301
|
-
},
|
|
313
|
+
const yaml = ElisymYamlSchema.parse({
|
|
314
|
+
display_name: displayName || void 0,
|
|
315
|
+
description,
|
|
316
|
+
picture: picture || void 0,
|
|
317
|
+
banner: banner || void 0,
|
|
302
318
|
relays: [...RELAYS],
|
|
303
|
-
payments: solanaAddress ? [{ chain: "solana", network, address: solanaAddress }] :
|
|
304
|
-
llm: {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
max_tokens: maxTokens
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
saveConfig(config);
|
|
312
|
-
const npub = nip19.npubEncode(nostrPubkey);
|
|
313
|
-
console.log(`
|
|
314
|
-
Agent "${name}" created.`);
|
|
315
|
-
console.log(` Nostr: ${npub}`);
|
|
316
|
-
if (solanaAddress) {
|
|
317
|
-
console.log(` Solana: ${solanaAddress}`);
|
|
318
|
-
}
|
|
319
|
-
if (passphrase) {
|
|
320
|
-
console.log(" Secrets encrypted with your passphrase.");
|
|
321
|
-
}
|
|
322
|
-
console.log(` Config: ~/.elisym/agents/${name}/config.json
|
|
323
|
-
`);
|
|
319
|
+
payments: solanaAddress ? [{ chain: "solana", network: "devnet", address: solanaAddress }] : [],
|
|
320
|
+
llm: { provider: llmProvider, model, max_tokens: maxTokens },
|
|
321
|
+
security: {}
|
|
322
|
+
});
|
|
323
|
+
return { yaml, apiKey: envKey ? void 0 : apiKey };
|
|
324
324
|
}
|
|
325
325
|
var FALLBACK_MODELS;
|
|
326
326
|
var init_init = __esm({
|
|
327
327
|
"src/commands/init.ts"() {
|
|
328
|
-
init_config();
|
|
329
328
|
FALLBACK_MODELS = {
|
|
330
329
|
anthropic: ["claude-sonnet-4-6", "claude-haiku-4-5-20251001", "claude-opus-4-6"],
|
|
331
330
|
openai: ["gpt-4o", "gpt-4o-mini", "o3-mini"]
|
|
@@ -335,41 +334,32 @@ var init_init = __esm({
|
|
|
335
334
|
|
|
336
335
|
// src/index.ts
|
|
337
336
|
init_init();
|
|
338
|
-
|
|
339
|
-
// src/commands/profile.ts
|
|
340
|
-
init_config();
|
|
341
|
-
async function resolveImage2(input, identity, media) {
|
|
342
|
-
if (!input) {
|
|
343
|
-
return "";
|
|
344
|
-
}
|
|
345
|
-
if (existsSync(input)) {
|
|
346
|
-
console.log(` Uploading ${basename(input)}...`);
|
|
347
|
-
const data = readFileSync(input);
|
|
348
|
-
const blob = new Blob([data]);
|
|
349
|
-
const url = await media.upload(identity, blob, basename(input));
|
|
350
|
-
console.log(` Uploaded: ${url}`);
|
|
351
|
-
return url;
|
|
352
|
-
}
|
|
353
|
-
return input;
|
|
354
|
-
}
|
|
355
337
|
async function cmdProfile(name) {
|
|
356
338
|
const { default: inquirer } = await import('inquirer');
|
|
339
|
+
const cwd = process.cwd();
|
|
357
340
|
if (!name) {
|
|
358
|
-
const agents = listAgents();
|
|
341
|
+
const agents = await listAgents(cwd);
|
|
359
342
|
if (agents.length === 0) {
|
|
360
343
|
console.error("No agents found. Run `elisym init` first.");
|
|
361
344
|
process.exit(1);
|
|
362
345
|
}
|
|
363
346
|
const { selected } = await inquirer.prompt([
|
|
364
|
-
{
|
|
347
|
+
{
|
|
348
|
+
type: "list",
|
|
349
|
+
name: "selected",
|
|
350
|
+
message: "Select agent:",
|
|
351
|
+
choices: agents.map((agent) => ({
|
|
352
|
+
name: `${agent.name} (${agent.source})`,
|
|
353
|
+
value: agent.name
|
|
354
|
+
}))
|
|
355
|
+
}
|
|
365
356
|
]);
|
|
366
357
|
name = selected;
|
|
367
358
|
}
|
|
368
359
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
369
|
-
const
|
|
370
|
-
const identity = ElisymIdentity.fromHex(config.identity.secret_key);
|
|
360
|
+
const loaded = await loadAgent(name, cwd, passphrase);
|
|
371
361
|
console.log(`
|
|
372
|
-
Editing agent "${name}"
|
|
362
|
+
Editing agent "${name}" (${loaded.source})
|
|
373
363
|
`);
|
|
374
364
|
let done = false;
|
|
375
365
|
while (!done) {
|
|
@@ -379,13 +369,16 @@ async function cmdProfile(name) {
|
|
|
379
369
|
name: "section",
|
|
380
370
|
message: "What to edit?",
|
|
381
371
|
choices: [
|
|
382
|
-
{ name: `Profile (name: ${config.identity.name})`, value: "profile" },
|
|
383
372
|
{
|
|
384
|
-
name: `
|
|
373
|
+
name: `Profile (description: ${truncate(loaded.yaml.description ?? "")})`,
|
|
374
|
+
value: "profile"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: `Wallet (${loaded.yaml.payments[0]?.address ?? "not configured"})`,
|
|
385
378
|
value: "wallet"
|
|
386
379
|
},
|
|
387
380
|
{
|
|
388
|
-
name: `LLM (${
|
|
381
|
+
name: `LLM (${loaded.yaml.llm?.provider ?? "not configured"} / ${loaded.yaml.llm?.model ?? "-"})`,
|
|
389
382
|
value: "llm"
|
|
390
383
|
},
|
|
391
384
|
{ name: "Done", value: "done" }
|
|
@@ -398,70 +391,64 @@ async function cmdProfile(name) {
|
|
|
398
391
|
}
|
|
399
392
|
if (section === "profile") {
|
|
400
393
|
const answers = await inquirer.prompt([
|
|
394
|
+
{
|
|
395
|
+
type: "input",
|
|
396
|
+
name: "displayName",
|
|
397
|
+
message: "Display name (for UI):",
|
|
398
|
+
default: loaded.yaml.display_name ?? ""
|
|
399
|
+
},
|
|
401
400
|
{
|
|
402
401
|
type: "input",
|
|
403
402
|
name: "description",
|
|
404
403
|
message: "Description:",
|
|
405
|
-
default:
|
|
404
|
+
default: loaded.yaml.description ?? ""
|
|
406
405
|
},
|
|
407
406
|
{
|
|
408
407
|
type: "input",
|
|
409
408
|
name: "picture",
|
|
410
|
-
message: "Avatar (
|
|
411
|
-
default:
|
|
409
|
+
message: "Avatar (relative path or URL, empty to clear):",
|
|
410
|
+
default: loaded.yaml.picture ?? ""
|
|
412
411
|
},
|
|
413
412
|
{
|
|
414
413
|
type: "input",
|
|
415
414
|
name: "banner",
|
|
416
|
-
message: "Banner (
|
|
417
|
-
default:
|
|
415
|
+
message: "Banner (relative path or URL, empty to clear):",
|
|
416
|
+
default: loaded.yaml.banner ?? ""
|
|
418
417
|
}
|
|
419
418
|
]);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
} else {
|
|
430
|
-
config.identity.banner = void 0;
|
|
431
|
-
}
|
|
432
|
-
saveConfig(config);
|
|
419
|
+
const nextYaml = {
|
|
420
|
+
...loaded.yaml,
|
|
421
|
+
display_name: answers.displayName || void 0,
|
|
422
|
+
description: answers.description ?? "",
|
|
423
|
+
picture: answers.picture || void 0,
|
|
424
|
+
banner: answers.banner || void 0
|
|
425
|
+
};
|
|
426
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
427
|
+
loaded.yaml = nextYaml;
|
|
433
428
|
console.log(" Profile updated.\n");
|
|
434
429
|
}
|
|
435
430
|
if (section === "wallet") {
|
|
436
|
-
const
|
|
437
|
-
const currentNetwork = config.payments?.[0]?.network ?? "devnet";
|
|
431
|
+
const current = loaded.yaml.payments[0];
|
|
438
432
|
const answers = await inquirer.prompt([
|
|
439
433
|
{
|
|
440
434
|
type: "input",
|
|
441
435
|
name: "address",
|
|
442
|
-
message: "Solana address:",
|
|
443
|
-
default:
|
|
444
|
-
validate: (
|
|
445
|
-
if (!
|
|
436
|
+
message: "Solana address (empty to clear):",
|
|
437
|
+
default: current?.address ?? "",
|
|
438
|
+
validate: (value) => {
|
|
439
|
+
if (!value) {
|
|
446
440
|
return true;
|
|
447
441
|
}
|
|
448
|
-
return isAddress(
|
|
442
|
+
return isAddress(value) || "Invalid Solana address";
|
|
449
443
|
}
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
type: "list",
|
|
453
|
-
name: "network",
|
|
454
|
-
message: "Network:",
|
|
455
|
-
choices: ["devnet"],
|
|
456
|
-
default: currentNetwork
|
|
457
444
|
}
|
|
458
445
|
]);
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
446
|
+
const nextYaml = {
|
|
447
|
+
...loaded.yaml,
|
|
448
|
+
payments: answers.address ? [{ chain: "solana", network: "devnet", address: answers.address }] : []
|
|
449
|
+
};
|
|
450
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
451
|
+
loaded.yaml = nextYaml;
|
|
465
452
|
console.log(" Wallet updated.\n");
|
|
466
453
|
}
|
|
467
454
|
if (section === "llm") {
|
|
@@ -474,33 +461,29 @@ async function cmdProfile(name) {
|
|
|
474
461
|
{ name: "Anthropic (Claude)", value: "anthropic" },
|
|
475
462
|
{ name: "OpenAI (GPT)", value: "openai" }
|
|
476
463
|
],
|
|
477
|
-
default:
|
|
464
|
+
default: loaded.yaml.llm?.provider ?? "anthropic"
|
|
478
465
|
}
|
|
479
466
|
]);
|
|
480
467
|
const { apiKey } = await inquirer.prompt([
|
|
481
468
|
{
|
|
482
469
|
type: "password",
|
|
483
470
|
name: "apiKey",
|
|
484
|
-
message:
|
|
471
|
+
message: "API key (leave empty to keep current):",
|
|
485
472
|
mask: "*"
|
|
486
473
|
}
|
|
487
474
|
]);
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
if (isEncrypted(plainKey) && passphrase) {
|
|
491
|
-
const { decryptSecret } = await import('@elisym/sdk/node');
|
|
492
|
-
plainKey = decryptSecret(plainKey, passphrase);
|
|
493
|
-
}
|
|
475
|
+
const currentKeyPlain = loaded.secrets.llm_api_key && !isEncrypted(loaded.secrets.llm_api_key) ? loaded.secrets.llm_api_key : "";
|
|
476
|
+
const probeKey = apiKey || currentKeyPlain;
|
|
494
477
|
console.log(" Fetching available models...");
|
|
495
478
|
const { fetchModels: fetchModels2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
496
|
-
const models = await fetchModels2(llmProvider,
|
|
479
|
+
const models = await fetchModels2(llmProvider, probeKey);
|
|
497
480
|
const { model } = await inquirer.prompt([
|
|
498
481
|
{
|
|
499
482
|
type: "list",
|
|
500
483
|
name: "model",
|
|
501
484
|
message: "Model:",
|
|
502
485
|
choices: models,
|
|
503
|
-
default:
|
|
486
|
+
default: loaded.yaml.llm?.model
|
|
504
487
|
}
|
|
505
488
|
]);
|
|
506
489
|
const { maxTokens } = await inquirer.prompt([
|
|
@@ -508,26 +491,109 @@ async function cmdProfile(name) {
|
|
|
508
491
|
type: "number",
|
|
509
492
|
name: "maxTokens",
|
|
510
493
|
message: "Max tokens:",
|
|
511
|
-
default:
|
|
494
|
+
default: loaded.yaml.llm?.max_tokens ?? 4096
|
|
512
495
|
}
|
|
513
496
|
]);
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
497
|
+
const nextLlm = { provider: llmProvider, model, max_tokens: maxTokens };
|
|
498
|
+
const nextYaml = { ...loaded.yaml, llm: nextLlm };
|
|
499
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
500
|
+
loaded.yaml = nextYaml;
|
|
501
|
+
if (apiKey) {
|
|
502
|
+
await writeSecrets(loaded.dir, { ...loaded.secrets, llm_api_key: apiKey }, passphrase);
|
|
503
|
+
loaded.secrets = { ...loaded.secrets, llm_api_key: apiKey };
|
|
504
|
+
}
|
|
522
505
|
console.log(" LLM updated.\n");
|
|
523
506
|
}
|
|
524
507
|
}
|
|
525
508
|
console.log(` Agent "${name}" saved.
|
|
526
509
|
`);
|
|
527
510
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
511
|
+
function truncate(value, max = 40) {
|
|
512
|
+
if (value.length <= max) {
|
|
513
|
+
return value;
|
|
514
|
+
}
|
|
515
|
+
return value.slice(0, max - 1) + "\u2026";
|
|
516
|
+
}
|
|
517
|
+
var TCP_PROBE_TIMEOUT_MS = 3e3;
|
|
518
|
+
function parseRelayUrl(url) {
|
|
519
|
+
try {
|
|
520
|
+
const parsed = new URL(url);
|
|
521
|
+
const isSecure = parsed.protocol === "wss:" || parsed.protocol === "https:";
|
|
522
|
+
const defaultPort = isSecure ? 443 : 80;
|
|
523
|
+
const port = parsed.port.length > 0 ? parseInt(parsed.port, 10) : defaultPort;
|
|
524
|
+
if (!Number.isFinite(port)) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
return { host: parsed.hostname, port };
|
|
528
|
+
} catch {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function tcpProbe(host, port, timeoutMs) {
|
|
533
|
+
return new Promise((resolve3) => {
|
|
534
|
+
const started = Date.now();
|
|
535
|
+
const socket = new Socket();
|
|
536
|
+
let settled = false;
|
|
537
|
+
const finish = (result) => {
|
|
538
|
+
if (settled) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
settled = true;
|
|
542
|
+
socket.destroy();
|
|
543
|
+
resolve3(result);
|
|
544
|
+
};
|
|
545
|
+
const timer = setTimeout(() => finish({ error: "timeout" }), timeoutMs);
|
|
546
|
+
socket.once("connect", () => {
|
|
547
|
+
clearTimeout(timer);
|
|
548
|
+
finish({ openMs: Date.now() - started });
|
|
549
|
+
});
|
|
550
|
+
socket.once("error", (err) => {
|
|
551
|
+
clearTimeout(timer);
|
|
552
|
+
finish({ error: err.message });
|
|
553
|
+
});
|
|
554
|
+
socket.connect(port, host);
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async function probeRelays(relays, logger, timeoutMs = TCP_PROBE_TIMEOUT_MS) {
|
|
558
|
+
const results = [];
|
|
559
|
+
for (const url of relays) {
|
|
560
|
+
const parsed = parseRelayUrl(url);
|
|
561
|
+
if (!parsed) {
|
|
562
|
+
logger.debug({ event: "net_diag_parse_failed", url }, "unparseable relay URL");
|
|
563
|
+
results.push({ url, host: "", port: 0, ips: [], error: "invalid URL" });
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const { host, port } = parsed;
|
|
567
|
+
let ips = [];
|
|
568
|
+
try {
|
|
569
|
+
const resolved = await lookup(host, { all: true });
|
|
570
|
+
ips = resolved.map((entry) => entry.address);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
573
|
+
logger.debug(
|
|
574
|
+
{ event: "net_diag_dns_failed", url, host, error: message },
|
|
575
|
+
"DNS lookup failed"
|
|
576
|
+
);
|
|
577
|
+
results.push({ url, host, port, ips: [], error: `dns: ${message}` });
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const probe = await tcpProbe(host, port, timeoutMs);
|
|
581
|
+
if ("openMs" in probe) {
|
|
582
|
+
logger.debug(
|
|
583
|
+
{ event: "net_diag_tcp_open", url, host, port, ips, tcpOpenMs: probe.openMs },
|
|
584
|
+
"TCP open"
|
|
585
|
+
);
|
|
586
|
+
results.push({ url, host, port, ips, tcpOpenMs: probe.openMs });
|
|
587
|
+
} else {
|
|
588
|
+
logger.debug(
|
|
589
|
+
{ event: "net_diag_tcp_failed", url, host, port, ips, error: probe.error },
|
|
590
|
+
"TCP probe failed"
|
|
591
|
+
);
|
|
592
|
+
results.push({ url, host, port, ips, error: `tcp: ${probe.error}` });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return results;
|
|
596
|
+
}
|
|
531
597
|
var HEARTBEAT_INTERVAL_MS = 10 * 60 * 1e3;
|
|
532
598
|
var MAX_CONCURRENT_JOBS = 10;
|
|
533
599
|
var RECOVERY_MAX_RETRIES = 5;
|
|
@@ -552,8 +618,13 @@ var VALID_TRANSITIONS = {
|
|
|
552
618
|
var JobLedger = class {
|
|
553
619
|
entries = /* @__PURE__ */ new Map();
|
|
554
620
|
path;
|
|
555
|
-
|
|
556
|
-
|
|
621
|
+
/**
|
|
622
|
+
* @param ledgerPath absolute path to the ledger file
|
|
623
|
+
* (typically `<agentDir>/.jobs.json`). Caller is responsible for
|
|
624
|
+
* resolving the agent directory via `@elisym/sdk/agent-store`.
|
|
625
|
+
*/
|
|
626
|
+
constructor(ledgerPath) {
|
|
627
|
+
this.path = ledgerPath;
|
|
557
628
|
this.load();
|
|
558
629
|
}
|
|
559
630
|
load() {
|
|
@@ -651,13 +722,29 @@ var JobLedger = class {
|
|
|
651
722
|
}
|
|
652
723
|
/** Remove old delivered/failed entries (default: 7 days). */
|
|
653
724
|
gc(maxAgeSecs = 7 * 24 * 60 * 60) {
|
|
654
|
-
|
|
725
|
+
this.pruneOldEntries(maxAgeSecs * 1e3);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Drop terminal entries (`delivered` / `failed`) whose `created_at`
|
|
729
|
+
* predates `now - retentionMs`. Stuck non-terminal entries are never
|
|
730
|
+
* pruned - recovery keeps retrying them until the retry budget runs
|
|
731
|
+
* out, then they become terminal and are eligible on the next sweep.
|
|
732
|
+
*
|
|
733
|
+
* Returns the number of entries deleted, for observability.
|
|
734
|
+
*/
|
|
735
|
+
pruneOldEntries(retentionMs) {
|
|
736
|
+
const cutoff = Math.floor(Date.now() / 1e3) - Math.floor(retentionMs / 1e3);
|
|
737
|
+
let deleted = 0;
|
|
655
738
|
for (const [id, entry] of this.entries) {
|
|
656
739
|
if ((entry.status === "delivered" || entry.status === "failed") && entry.created_at < cutoff) {
|
|
657
740
|
this.entries.delete(id);
|
|
741
|
+
deleted += 1;
|
|
658
742
|
}
|
|
659
743
|
}
|
|
660
|
-
|
|
744
|
+
if (deleted > 0) {
|
|
745
|
+
this.flush();
|
|
746
|
+
}
|
|
747
|
+
return deleted;
|
|
661
748
|
}
|
|
662
749
|
};
|
|
663
750
|
|
|
@@ -677,7 +764,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
677
764
|
if (!signal) {
|
|
678
765
|
return new Promise((r) => setTimeout(r, ms));
|
|
679
766
|
}
|
|
680
|
-
return new Promise((
|
|
767
|
+
return new Promise((resolve3, reject) => {
|
|
681
768
|
const cleanup = () => {
|
|
682
769
|
clearTimeout(timer);
|
|
683
770
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -688,7 +775,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
688
775
|
};
|
|
689
776
|
const timer = setTimeout(() => {
|
|
690
777
|
cleanup();
|
|
691
|
-
|
|
778
|
+
resolve3();
|
|
692
779
|
}, ms);
|
|
693
780
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
694
781
|
});
|
|
@@ -953,8 +1040,56 @@ var OpenAIClient = class {
|
|
|
953
1040
|
}));
|
|
954
1041
|
}
|
|
955
1042
|
};
|
|
1043
|
+
function resolveLevel(options) {
|
|
1044
|
+
if (options.level) {
|
|
1045
|
+
return options.level;
|
|
1046
|
+
}
|
|
1047
|
+
if (options.verbose) {
|
|
1048
|
+
return "debug";
|
|
1049
|
+
}
|
|
1050
|
+
const envLevel = process.env.LOG_LEVEL;
|
|
1051
|
+
if (envLevel) {
|
|
1052
|
+
return envLevel;
|
|
1053
|
+
}
|
|
1054
|
+
return "info";
|
|
1055
|
+
}
|
|
1056
|
+
function createLogger(options = {}) {
|
|
1057
|
+
const level = resolveLevel(options);
|
|
1058
|
+
const baseOptions = {
|
|
1059
|
+
name: "elisym-cli",
|
|
1060
|
+
level,
|
|
1061
|
+
redact: {
|
|
1062
|
+
paths: DEFAULT_REDACT_PATHS,
|
|
1063
|
+
censor: makeCensor()
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
let logger;
|
|
1067
|
+
if (options.destination) {
|
|
1068
|
+
logger = pino(baseOptions, options.destination);
|
|
1069
|
+
} else if (options.tty === true) {
|
|
1070
|
+
logger = pino({
|
|
1071
|
+
...baseOptions,
|
|
1072
|
+
transport: {
|
|
1073
|
+
target: "pino-pretty",
|
|
1074
|
+
options: { destination: 2, colorize: true, translateTime: "HH:MM:ss" }
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
} else {
|
|
1078
|
+
logger = pino(baseOptions, pino.destination(2));
|
|
1079
|
+
}
|
|
1080
|
+
function logWithIndent(line) {
|
|
1081
|
+
process.stdout.write(` ${line}
|
|
1082
|
+
`);
|
|
1083
|
+
}
|
|
1084
|
+
return {
|
|
1085
|
+
logger,
|
|
1086
|
+
logWithIndent,
|
|
1087
|
+
bannerLog: logWithIndent
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
956
1090
|
var payment = new SolanaPaymentStrategy();
|
|
957
1091
|
var LEDGER_GC_INTERVAL_MS = 60 * 60 * 1e3;
|
|
1092
|
+
var LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
958
1093
|
var TOTAL_JOB_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
959
1094
|
function resolveJobPrice(tags, skills) {
|
|
960
1095
|
const skill = skills.route(tags);
|
|
@@ -963,6 +1098,8 @@ function resolveJobPrice(tags, skills) {
|
|
|
963
1098
|
var RATE_LIMIT_WINDOW_MS = 10 * 60 * 1e3;
|
|
964
1099
|
var MAX_JOBS_PER_CUSTOMER = 20;
|
|
965
1100
|
var GLOBAL_MAX_JOBS_PER_WINDOW = 200;
|
|
1101
|
+
var MAX_TRACKED_CUSTOMERS = 1e3;
|
|
1102
|
+
var GLOBAL_LIMITER_KEY = "__global__";
|
|
966
1103
|
var AgentRuntime = class {
|
|
967
1104
|
constructor(transport, skills, skillCtx, config, ledger, callbacks = {}) {
|
|
968
1105
|
this.transport = transport;
|
|
@@ -983,10 +1120,18 @@ var AgentRuntime = class {
|
|
|
983
1120
|
recoveryInterval = null;
|
|
984
1121
|
gcInterval = null;
|
|
985
1122
|
stopped = false;
|
|
986
|
-
/** Per-customer sliding
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1123
|
+
/** Per-customer sliding-window rate limiter (keyed on customer pubkey). */
|
|
1124
|
+
customerLimiter = createSlidingWindowLimiter({
|
|
1125
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
1126
|
+
maxPerWindow: MAX_JOBS_PER_CUSTOMER,
|
|
1127
|
+
maxKeys: MAX_TRACKED_CUSTOMERS
|
|
1128
|
+
});
|
|
1129
|
+
/** Global sliding-window rate limiter (Sybil protection). */
|
|
1130
|
+
globalLimiter = createSlidingWindowLimiter({
|
|
1131
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
1132
|
+
maxPerWindow: GLOBAL_MAX_JOBS_PER_WINDOW,
|
|
1133
|
+
maxKeys: 1
|
|
1134
|
+
});
|
|
990
1135
|
/** Fetch on-chain protocol config (fee, treasury). Always fetches fresh to avoid stale treasury. */
|
|
991
1136
|
async fetchProtocolConfig() {
|
|
992
1137
|
if (this.config.network !== "devnet") {
|
|
@@ -1001,7 +1146,7 @@ var AgentRuntime = class {
|
|
|
1001
1146
|
}
|
|
1002
1147
|
async run() {
|
|
1003
1148
|
const log = this.callbacks.onLog ?? console.log;
|
|
1004
|
-
this.ledger.
|
|
1149
|
+
this.ledger.pruneOldEntries(LEDGER_RETENTION_MS);
|
|
1005
1150
|
await this.recoverPendingJobs();
|
|
1006
1151
|
this.recoveryInterval = setInterval(
|
|
1007
1152
|
() => this.recoverPendingJobs().catch((e) => log(`Recovery error: ${e}`)),
|
|
@@ -1009,7 +1154,7 @@ var AgentRuntime = class {
|
|
|
1009
1154
|
);
|
|
1010
1155
|
this.gcInterval = setInterval(() => {
|
|
1011
1156
|
try {
|
|
1012
|
-
this.ledger.
|
|
1157
|
+
this.ledger.pruneOldEntries(LEDGER_RETENTION_MS);
|
|
1013
1158
|
} catch (e) {
|
|
1014
1159
|
log(`GC error: ${e.message}`);
|
|
1015
1160
|
}
|
|
@@ -1033,17 +1178,18 @@ var AgentRuntime = class {
|
|
|
1033
1178
|
});
|
|
1034
1179
|
return;
|
|
1035
1180
|
}
|
|
1036
|
-
if (this.
|
|
1037
|
-
this.transport.sendFeedback(job, { type: "error", message: "
|
|
1181
|
+
if (!this.customerLimiter.peek(job.customerId).allowed) {
|
|
1182
|
+
this.transport.sendFeedback(job, { type: "error", message: "Rate limited, try again later" }).catch(() => {
|
|
1038
1183
|
});
|
|
1039
1184
|
return;
|
|
1040
1185
|
}
|
|
1041
|
-
if (this.
|
|
1042
|
-
this.transport.sendFeedback(job, { type: "error", message: "
|
|
1186
|
+
if (!this.globalLimiter.peek(GLOBAL_LIMITER_KEY).allowed) {
|
|
1187
|
+
this.transport.sendFeedback(job, { type: "error", message: "Server busy, try again later" }).catch(() => {
|
|
1043
1188
|
});
|
|
1044
1189
|
return;
|
|
1045
1190
|
}
|
|
1046
|
-
this.
|
|
1191
|
+
this.customerLimiter.check(job.customerId);
|
|
1192
|
+
this.globalLimiter.check(GLOBAL_LIMITER_KEY);
|
|
1047
1193
|
this.callbacks.onJobReceived?.(job);
|
|
1048
1194
|
this.inFlight.add(job.jobId);
|
|
1049
1195
|
this.pending++;
|
|
@@ -1055,8 +1201,8 @@ var AgentRuntime = class {
|
|
|
1055
1201
|
});
|
|
1056
1202
|
});
|
|
1057
1203
|
log("Agent runtime started. Listening for jobs...");
|
|
1058
|
-
await new Promise((
|
|
1059
|
-
this.abortController.signal.addEventListener("abort", () =>
|
|
1204
|
+
await new Promise((resolve3) => {
|
|
1205
|
+
this.abortController.signal.addEventListener("abort", () => resolve3(), { once: true });
|
|
1060
1206
|
process.on("SIGINT", () => {
|
|
1061
1207
|
log("Shutting down...");
|
|
1062
1208
|
this.stop();
|
|
@@ -1069,40 +1215,10 @@ var AgentRuntime = class {
|
|
|
1069
1215
|
});
|
|
1070
1216
|
});
|
|
1071
1217
|
}
|
|
1072
|
-
/**
|
|
1073
|
-
isGlobalRateLimited() {
|
|
1074
|
-
const now = Date.now();
|
|
1075
|
-
this.globalJobs = this.globalJobs.filter((t) => now - t < RATE_LIMIT_WINDOW_MS);
|
|
1076
|
-
return this.globalJobs.length >= GLOBAL_MAX_JOBS_PER_WINDOW;
|
|
1077
|
-
}
|
|
1078
|
-
/** Returns true if customer has exceeded the per-window job limit. Pure check - no side effects. */
|
|
1079
|
-
isCustomerRateLimited(customerId) {
|
|
1080
|
-
const now = Date.now();
|
|
1081
|
-
const timestamps = this.customerJobs.get(customerId) ?? [];
|
|
1082
|
-
const recent = timestamps.filter((t) => now - t < RATE_LIMIT_WINDOW_MS);
|
|
1083
|
-
this.customerJobs.set(customerId, recent);
|
|
1084
|
-
return recent.length >= MAX_JOBS_PER_CUSTOMER;
|
|
1085
|
-
}
|
|
1086
|
-
/** Record a job acceptance for rate limiting. Called only after all checks pass. */
|
|
1087
|
-
recordJobAccepted(customerId) {
|
|
1088
|
-
const now = Date.now();
|
|
1089
|
-
this.globalJobs.push(now);
|
|
1090
|
-
const timestamps = this.customerJobs.get(customerId) ?? [];
|
|
1091
|
-
timestamps.push(now);
|
|
1092
|
-
this.customerJobs.set(customerId, timestamps);
|
|
1093
|
-
}
|
|
1094
|
-
/** Clean up expired rate limit entries. */
|
|
1218
|
+
/** Drop expired hits from both sliding-window limiters. */
|
|
1095
1219
|
cleanupRateLimits() {
|
|
1096
|
-
|
|
1097
|
-
this.
|
|
1098
|
-
for (const [key, timestamps] of this.customerJobs) {
|
|
1099
|
-
const recent = timestamps.filter((t) => now - t < RATE_LIMIT_WINDOW_MS);
|
|
1100
|
-
if (recent.length === 0) {
|
|
1101
|
-
this.customerJobs.delete(key);
|
|
1102
|
-
} else {
|
|
1103
|
-
this.customerJobs.set(key, recent);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1220
|
+
this.customerLimiter.prune();
|
|
1221
|
+
this.globalLimiter.prune();
|
|
1106
1222
|
}
|
|
1107
1223
|
stop() {
|
|
1108
1224
|
if (this.stopped) {
|
|
@@ -1479,7 +1595,6 @@ var SkillRegistry = class {
|
|
|
1479
1595
|
return this.skills;
|
|
1480
1596
|
}
|
|
1481
1597
|
};
|
|
1482
|
-
var MAX_TOOL_OUTPUT = 1e6;
|
|
1483
1598
|
var ScriptSkill = class {
|
|
1484
1599
|
name;
|
|
1485
1600
|
description;
|
|
@@ -1487,10 +1602,7 @@ var ScriptSkill = class {
|
|
|
1487
1602
|
priceLamports;
|
|
1488
1603
|
image;
|
|
1489
1604
|
imageFile;
|
|
1490
|
-
|
|
1491
|
-
systemPrompt;
|
|
1492
|
-
tools;
|
|
1493
|
-
maxToolRounds;
|
|
1605
|
+
inner;
|
|
1494
1606
|
constructor(name, description, capabilities, priceLamports, image, imageFile, skillDir, systemPrompt, tools, maxToolRounds) {
|
|
1495
1607
|
this.name = name;
|
|
1496
1608
|
this.description = description;
|
|
@@ -1498,155 +1610,25 @@ var ScriptSkill = class {
|
|
|
1498
1610
|
this.priceLamports = priceLamports;
|
|
1499
1611
|
this.image = image;
|
|
1500
1612
|
this.imageFile = imageFile;
|
|
1501
|
-
this.
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1613
|
+
this.inner = new ScriptSkill$1({
|
|
1614
|
+
name,
|
|
1615
|
+
description,
|
|
1616
|
+
capabilities,
|
|
1617
|
+
priceLamports: BigInt(Math.round(priceLamports)),
|
|
1618
|
+
skillDir,
|
|
1619
|
+
systemPrompt,
|
|
1620
|
+
tools,
|
|
1621
|
+
maxToolRounds,
|
|
1622
|
+
image,
|
|
1623
|
+
imageFile
|
|
1624
|
+
});
|
|
1505
1625
|
}
|
|
1506
1626
|
async execute(input, ctx) {
|
|
1507
|
-
|
|
1508
|
-
throw new Error("LLM client not configured");
|
|
1509
|
-
}
|
|
1510
|
-
if (this.tools.length === 0) {
|
|
1511
|
-
const result = await ctx.llm.complete(this.systemPrompt, input.data, ctx.signal);
|
|
1512
|
-
return { data: result };
|
|
1513
|
-
}
|
|
1514
|
-
const toolDefs = this.tools.map((t) => ({
|
|
1515
|
-
name: t.name,
|
|
1516
|
-
description: t.description,
|
|
1517
|
-
parameters: (t.parameters ?? []).map((p) => ({
|
|
1518
|
-
name: p.name,
|
|
1519
|
-
description: p.description,
|
|
1520
|
-
required: p.required ?? true
|
|
1521
|
-
}))
|
|
1522
|
-
}));
|
|
1523
|
-
const messages = [{ role: "user", content: input.data }];
|
|
1524
|
-
for (let round = 0; round < this.maxToolRounds; round++) {
|
|
1525
|
-
if (ctx.signal?.aborted) {
|
|
1526
|
-
throw new Error("Job aborted");
|
|
1527
|
-
}
|
|
1528
|
-
const result = await ctx.llm.completeWithTools(
|
|
1529
|
-
this.systemPrompt,
|
|
1530
|
-
messages,
|
|
1531
|
-
toolDefs,
|
|
1532
|
-
ctx.signal
|
|
1533
|
-
);
|
|
1534
|
-
if (result.type === "text") {
|
|
1535
|
-
return { data: result.text };
|
|
1536
|
-
}
|
|
1537
|
-
messages.push(result.assistantMessage);
|
|
1538
|
-
const toolResults = [];
|
|
1539
|
-
for (const call of result.calls) {
|
|
1540
|
-
const toolDef = this.tools.find((t) => t.name === call.name);
|
|
1541
|
-
if (!toolDef) {
|
|
1542
|
-
toolResults.push({
|
|
1543
|
-
callId: call.id,
|
|
1544
|
-
content: `Error: unknown tool "${call.name}"`
|
|
1545
|
-
});
|
|
1546
|
-
continue;
|
|
1547
|
-
}
|
|
1548
|
-
const output = await this.runTool(toolDef, call, ctx.signal);
|
|
1549
|
-
toolResults.push({ callId: call.id, content: output });
|
|
1550
|
-
}
|
|
1551
|
-
messages.push(...ctx.llm.formatToolResultMessages(toolResults));
|
|
1552
|
-
}
|
|
1553
|
-
throw new Error(`Max tool rounds (${this.maxToolRounds}) exceeded`);
|
|
1554
|
-
}
|
|
1555
|
-
runTool(toolDef, call, signal) {
|
|
1556
|
-
return new Promise((resolve, _reject) => {
|
|
1557
|
-
const args = [...toolDef.command];
|
|
1558
|
-
const cmd = args.shift();
|
|
1559
|
-
const params = toolDef.parameters ?? [];
|
|
1560
|
-
for (const param of params) {
|
|
1561
|
-
const value = call.arguments[param.name];
|
|
1562
|
-
if (value !== void 0) {
|
|
1563
|
-
if (param.required && params.indexOf(param) === 0) {
|
|
1564
|
-
args.push(String(value));
|
|
1565
|
-
} else {
|
|
1566
|
-
args.push(`--${param.name}`, String(value));
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
const child = spawn(cmd, args, {
|
|
1571
|
-
cwd: this.skillDir,
|
|
1572
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1573
|
-
timeout: 6e4,
|
|
1574
|
-
killSignal: "SIGKILL",
|
|
1575
|
-
signal
|
|
1576
|
-
});
|
|
1577
|
-
let stdout = "";
|
|
1578
|
-
let stderr = "";
|
|
1579
|
-
const stdoutDecoder = new StringDecoder("utf8");
|
|
1580
|
-
const stderrDecoder = new StringDecoder("utf8");
|
|
1581
|
-
child.stdout.on("data", (data) => {
|
|
1582
|
-
if (stdout.length < MAX_TOOL_OUTPUT) {
|
|
1583
|
-
stdout += stdoutDecoder.write(data);
|
|
1584
|
-
if (stdout.length > MAX_TOOL_OUTPUT) {
|
|
1585
|
-
stdout = stdout.slice(0, MAX_TOOL_OUTPUT);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
});
|
|
1589
|
-
child.stderr.on("data", (data) => {
|
|
1590
|
-
if (stderr.length < MAX_TOOL_OUTPUT) {
|
|
1591
|
-
stderr += stderrDecoder.write(data);
|
|
1592
|
-
if (stderr.length > MAX_TOOL_OUTPUT) {
|
|
1593
|
-
stderr = stderr.slice(0, MAX_TOOL_OUTPUT);
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
});
|
|
1597
|
-
child.on("close", (code) => {
|
|
1598
|
-
if (code === 0) {
|
|
1599
|
-
resolve(stdout.trim());
|
|
1600
|
-
} else {
|
|
1601
|
-
resolve(`Error (exit ${code}): ${stderr.trim() || stdout.trim()}`);
|
|
1602
|
-
}
|
|
1603
|
-
});
|
|
1604
|
-
child.on("error", (err) => {
|
|
1605
|
-
resolve(`Error: ${err.message}`);
|
|
1606
|
-
});
|
|
1607
|
-
});
|
|
1627
|
+
return this.inner.execute(input, ctx);
|
|
1608
1628
|
}
|
|
1609
1629
|
};
|
|
1610
1630
|
|
|
1611
1631
|
// src/skill/loader.ts
|
|
1612
|
-
var LAMPORTS_PER_SOL = 1e9;
|
|
1613
|
-
function solToLamports(sol) {
|
|
1614
|
-
if (!Number.isFinite(sol) || sol < 0) {
|
|
1615
|
-
return 0;
|
|
1616
|
-
}
|
|
1617
|
-
return Math.round(sol * LAMPORTS_PER_SOL);
|
|
1618
|
-
}
|
|
1619
|
-
function parseSkillMd(content) {
|
|
1620
|
-
const lines = content.split("\n");
|
|
1621
|
-
let start = -1;
|
|
1622
|
-
let end = -1;
|
|
1623
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1624
|
-
if (lines[i].trim() === "---") {
|
|
1625
|
-
if (start === -1) {
|
|
1626
|
-
start = i;
|
|
1627
|
-
} else {
|
|
1628
|
-
end = i;
|
|
1629
|
-
break;
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
if (start === -1 || end === -1) {
|
|
1634
|
-
throw new Error("SKILL.md must have YAML frontmatter between --- delimiters");
|
|
1635
|
-
}
|
|
1636
|
-
const yamlStr = lines.slice(start + 1, end).join("\n");
|
|
1637
|
-
const frontmatter = YAML.parse(yamlStr);
|
|
1638
|
-
if (!frontmatter.name || typeof frontmatter.name !== "string") {
|
|
1639
|
-
throw new Error('SKILL.md: missing or invalid "name" field');
|
|
1640
|
-
}
|
|
1641
|
-
if (!frontmatter.description || typeof frontmatter.description !== "string") {
|
|
1642
|
-
throw new Error('SKILL.md: missing or invalid "description" field');
|
|
1643
|
-
}
|
|
1644
|
-
if (!Array.isArray(frontmatter.capabilities) || frontmatter.capabilities.length === 0) {
|
|
1645
|
-
throw new Error('SKILL.md: "capabilities" must be a non-empty array');
|
|
1646
|
-
}
|
|
1647
|
-
const systemPrompt = lines.slice(end + 1).join("\n").trim();
|
|
1648
|
-
return { frontmatter, systemPrompt };
|
|
1649
|
-
}
|
|
1650
1632
|
function loadSkillsFromDir(skillsDir) {
|
|
1651
1633
|
const skills = [];
|
|
1652
1634
|
let entries;
|
|
@@ -1668,22 +1650,26 @@ function loadSkillsFromDir(skillsDir) {
|
|
|
1668
1650
|
try {
|
|
1669
1651
|
const content = readFileSync(skillMdPath, "utf-8");
|
|
1670
1652
|
const { frontmatter, systemPrompt } = parseSkillMd(content);
|
|
1653
|
+
const parsed = validateSkillFrontmatter(frontmatter, systemPrompt, {
|
|
1654
|
+
allowFreeSkills: true
|
|
1655
|
+
});
|
|
1671
1656
|
skills.push(
|
|
1672
1657
|
new ScriptSkill(
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1658
|
+
parsed.name,
|
|
1659
|
+
parsed.description,
|
|
1660
|
+
parsed.capabilities,
|
|
1661
|
+
Number(parsed.priceLamports),
|
|
1662
|
+
parsed.image,
|
|
1663
|
+
parsed.imageFile,
|
|
1679
1664
|
entryPath,
|
|
1680
|
-
systemPrompt,
|
|
1681
|
-
|
|
1682
|
-
|
|
1665
|
+
parsed.systemPrompt,
|
|
1666
|
+
parsed.tools,
|
|
1667
|
+
parsed.maxToolRounds
|
|
1683
1668
|
)
|
|
1684
1669
|
);
|
|
1685
1670
|
} catch (e) {
|
|
1686
|
-
|
|
1671
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1672
|
+
console.warn(` ! Skipping skill "${entry}": ${message}`);
|
|
1687
1673
|
}
|
|
1688
1674
|
}
|
|
1689
1675
|
return skills;
|
|
@@ -1802,6 +1788,7 @@ function startWatchdog(deps) {
|
|
|
1802
1788
|
transport,
|
|
1803
1789
|
onPing,
|
|
1804
1790
|
log,
|
|
1791
|
+
logger,
|
|
1805
1792
|
probeIntervalMs = WATCHDOG_PROBE_INTERVAL_MS,
|
|
1806
1793
|
probeTimeoutMs = WATCHDOG_PROBE_TIMEOUT_MS,
|
|
1807
1794
|
selfPingIntervalMs = WATCHDOG_SELF_PING_INTERVAL_MS,
|
|
@@ -1827,10 +1814,12 @@ function startWatchdog(deps) {
|
|
|
1827
1814
|
return;
|
|
1828
1815
|
}
|
|
1829
1816
|
log("[watchdog] relay probe failed, resetting pool and re-subscribing");
|
|
1817
|
+
logger?.info({ event: "pool_reset", reason: "probe_failed" }, "pool reset");
|
|
1830
1818
|
resetPoolAndResubscribe();
|
|
1831
1819
|
} catch (error) {
|
|
1832
1820
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1833
1821
|
log(`[watchdog] probe/reset error: ${errorMessage}`);
|
|
1822
|
+
logger?.warn({ event: "probe_error", error: errorMessage }, "watchdog probe/reset error");
|
|
1834
1823
|
} finally {
|
|
1835
1824
|
busy = false;
|
|
1836
1825
|
}
|
|
@@ -1846,10 +1835,15 @@ function startWatchdog(deps) {
|
|
|
1846
1835
|
return;
|
|
1847
1836
|
}
|
|
1848
1837
|
log("[watchdog] self-ping failed, resetting pool and re-subscribing");
|
|
1838
|
+
logger?.info({ event: "pool_reset", reason: "self_ping_failed" }, "pool reset");
|
|
1849
1839
|
resetPoolAndResubscribe();
|
|
1850
1840
|
} catch (error) {
|
|
1851
1841
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1852
1842
|
log(`[watchdog] self-ping/reset error: ${errorMessage}`);
|
|
1843
|
+
logger?.warn(
|
|
1844
|
+
{ event: "self_ping_error", error: errorMessage },
|
|
1845
|
+
"watchdog self-ping/reset error"
|
|
1846
|
+
);
|
|
1853
1847
|
} finally {
|
|
1854
1848
|
busy = false;
|
|
1855
1849
|
}
|
|
@@ -1868,37 +1862,33 @@ function startWatchdog(deps) {
|
|
|
1868
1862
|
}
|
|
1869
1863
|
|
|
1870
1864
|
// src/commands/start.ts
|
|
1871
|
-
async function cmdStart(
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
process.exit(1);
|
|
1877
|
-
}
|
|
1878
|
-
const { default: inquirer } = await import('inquirer');
|
|
1879
|
-
const choices = [...agents, { name: "+ Create new agent", value: "__new__" }];
|
|
1880
|
-
const { selected } = await inquirer.prompt([
|
|
1881
|
-
{ type: "list", name: "selected", message: "Select agent to start", choices }
|
|
1882
|
-
]);
|
|
1883
|
-
if (selected === "__new__") {
|
|
1884
|
-
const { cmdInit: cmdInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
1885
|
-
await cmdInit2();
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
name = selected;
|
|
1865
|
+
async function cmdStart(nameArg, options = {}) {
|
|
1866
|
+
const cwd = process.cwd();
|
|
1867
|
+
const agentName = await resolveStartAgentName(nameArg, cwd);
|
|
1868
|
+
if (!agentName) {
|
|
1869
|
+
return;
|
|
1889
1870
|
}
|
|
1890
|
-
const
|
|
1871
|
+
const loaded = await loadAgentWithPrompt(agentName, cwd);
|
|
1872
|
+
const verbose = options.verbose === true || process.env.ELISYM_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
|
|
1873
|
+
const { logger, logWithIndent } = createLogger({
|
|
1874
|
+
verbose,
|
|
1875
|
+
tty: Boolean(process.stdout.isTTY)
|
|
1876
|
+
});
|
|
1891
1877
|
console.log(`
|
|
1892
|
-
Starting agent ${
|
|
1878
|
+
Starting agent ${agentName} (${loaded.source})...
|
|
1893
1879
|
`);
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1880
|
+
if (loaded.shadowsGlobal) {
|
|
1881
|
+
console.log(
|
|
1882
|
+
` ! Using project-local ${agentName} (shadows global agent in ~/.elisym/${agentName}/)
|
|
1883
|
+
`
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
if (verbose) {
|
|
1887
|
+
console.log(" [debug] Verbose logging enabled. Structured diagnostics -> stderr.");
|
|
1900
1888
|
}
|
|
1901
|
-
const
|
|
1889
|
+
const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
1890
|
+
const solanaAddress = solPayment?.address;
|
|
1891
|
+
const walletNetwork = solPayment?.network ?? "devnet";
|
|
1902
1892
|
if (solanaAddress) {
|
|
1903
1893
|
try {
|
|
1904
1894
|
const rpcUrl = getRpcUrl(walletNetwork);
|
|
@@ -1922,17 +1912,26 @@ async function cmdStart(name) {
|
|
|
1922
1912
|
`);
|
|
1923
1913
|
}
|
|
1924
1914
|
}
|
|
1925
|
-
if (!
|
|
1915
|
+
if (!loaded.yaml.llm) {
|
|
1926
1916
|
console.error(" ! No LLM configured. Run `elisym init` to set up LLM.\n");
|
|
1927
1917
|
process.exit(1);
|
|
1928
1918
|
}
|
|
1929
|
-
|
|
1919
|
+
if (!loaded.secrets.llm_api_key) {
|
|
1920
|
+
console.error(
|
|
1921
|
+
` ! No LLM API key. Set ${loaded.yaml.llm.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"} env var or update the agent's secrets.
|
|
1922
|
+
`
|
|
1923
|
+
);
|
|
1924
|
+
process.exit(1);
|
|
1925
|
+
}
|
|
1926
|
+
const paths = agentPaths(loaded.dir);
|
|
1927
|
+
const skillsDir = paths.skills;
|
|
1930
1928
|
const allSkills = loadSkillsFromDir(skillsDir);
|
|
1931
1929
|
if (allSkills.length === 0) {
|
|
1932
1930
|
console.error(` ! No skills found in ${skillsDir}
|
|
1933
1931
|
`);
|
|
1934
1932
|
console.error(" Create a skill directory with a SKILL.md to get started.");
|
|
1935
|
-
console.error(
|
|
1933
|
+
console.error(` Example: ${skillsDir}/my-skill/SKILL.md
|
|
1934
|
+
`);
|
|
1936
1935
|
process.exit(1);
|
|
1937
1936
|
}
|
|
1938
1937
|
const registry = new SkillRegistry();
|
|
@@ -1948,53 +1947,88 @@ async function cmdStart(name) {
|
|
|
1948
1947
|
process.exit(1);
|
|
1949
1948
|
}
|
|
1950
1949
|
const llm = createLlmClient({
|
|
1951
|
-
provider:
|
|
1952
|
-
apiKey:
|
|
1953
|
-
model:
|
|
1954
|
-
maxTokens:
|
|
1950
|
+
provider: loaded.yaml.llm.provider,
|
|
1951
|
+
apiKey: loaded.secrets.llm_api_key,
|
|
1952
|
+
model: loaded.yaml.llm.model,
|
|
1953
|
+
maxTokens: loaded.yaml.llm.max_tokens,
|
|
1955
1954
|
logUsage: true
|
|
1956
1955
|
});
|
|
1957
1956
|
const skillCtx = {
|
|
1958
1957
|
llm,
|
|
1959
|
-
agentName
|
|
1960
|
-
agentDescription:
|
|
1958
|
+
agentName,
|
|
1959
|
+
agentDescription: loaded.yaml.description ?? ""
|
|
1961
1960
|
};
|
|
1962
1961
|
console.log(" Connecting to relays and publishing capabilities...");
|
|
1963
|
-
const identity = ElisymIdentity.fromHex(
|
|
1964
|
-
const relays =
|
|
1962
|
+
const identity = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
1963
|
+
const relays = loaded.yaml.relays.length > 0 ? loaded.yaml.relays : [...RELAYS];
|
|
1965
1964
|
const client = new ElisymClient({ relays });
|
|
1965
|
+
if (process.env.ELISYM_NET_DIAG === "1") {
|
|
1966
|
+
console.log(" [net-diag] Probing relay DNS + TCP connectivity...");
|
|
1967
|
+
const results = await probeRelays(relays, logger);
|
|
1968
|
+
for (const result of results) {
|
|
1969
|
+
const ipSummary = result.ips.length > 0 ? result.ips.join(",") : "-";
|
|
1970
|
+
if (result.tcpOpenMs !== void 0) {
|
|
1971
|
+
console.log(` [net-diag] ${result.url} -> ${ipSummary} TCP open in ${result.tcpOpenMs}ms`);
|
|
1972
|
+
} else {
|
|
1973
|
+
console.log(` [net-diag] ${result.url} -> ${ipSummary} FAILED: ${result.error ?? "?"}`);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1966
1977
|
const media = new MediaService();
|
|
1978
|
+
const mediaCache = await readMediaCache(loaded.dir);
|
|
1979
|
+
let mediaCacheDirty = false;
|
|
1980
|
+
const pictureUrl = await resolveMediaField(
|
|
1981
|
+
loaded.yaml.picture,
|
|
1982
|
+
loaded.dir,
|
|
1983
|
+
mediaCache,
|
|
1984
|
+
media,
|
|
1985
|
+
identity,
|
|
1986
|
+
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
1987
|
+
);
|
|
1988
|
+
const bannerUrl = await resolveMediaField(
|
|
1989
|
+
loaded.yaml.banner,
|
|
1990
|
+
loaded.dir,
|
|
1991
|
+
mediaCache,
|
|
1992
|
+
media,
|
|
1993
|
+
identity,
|
|
1994
|
+
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
1995
|
+
);
|
|
1967
1996
|
for (const skill of allSkills) {
|
|
1968
1997
|
if (skill.image || !skill.imageFile) {
|
|
1969
1998
|
continue;
|
|
1970
1999
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2000
|
+
const skillRoot = join(skillsDir, skill.name);
|
|
2001
|
+
const cacheKey = `./skills/${skill.name}/${skill.imageFile}`;
|
|
2002
|
+
const absPath = join(skillRoot, skill.imageFile);
|
|
2003
|
+
const url = await uploadOrReuse(
|
|
2004
|
+
cacheKey,
|
|
2005
|
+
absPath,
|
|
2006
|
+
mediaCache,
|
|
2007
|
+
media,
|
|
2008
|
+
identity,
|
|
2009
|
+
() => mediaCacheDirty = true
|
|
2010
|
+
);
|
|
2011
|
+
if (url) {
|
|
1978
2012
|
skill.image = url;
|
|
1979
|
-
const skillMdPath = join(skillsDir, skill.name, "SKILL.md");
|
|
1980
|
-
const mdContent = readFileSync(skillMdPath, "utf-8");
|
|
1981
|
-
const updated = mdContent.replace(/^(image_file:\s*.+)$/m, (m) => `${m}
|
|
1982
|
-
image: ${url}`);
|
|
1983
|
-
writeFileSync(skillMdPath, updated);
|
|
1984
|
-
} catch (e) {
|
|
1985
|
-
console.warn(` ! Failed to upload image for "${skill.name}": ${e.message}`);
|
|
1986
2013
|
}
|
|
1987
2014
|
}
|
|
2015
|
+
if (mediaCacheDirty) {
|
|
2016
|
+
await writeMediaCache(loaded.dir, mediaCache);
|
|
2017
|
+
}
|
|
2018
|
+
let profilePublished = false;
|
|
1988
2019
|
try {
|
|
1989
2020
|
await client.discovery.publishProfile(
|
|
1990
2021
|
identity,
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2022
|
+
agentName,
|
|
2023
|
+
loaded.yaml.description ?? "",
|
|
2024
|
+
pictureUrl,
|
|
2025
|
+
bannerUrl
|
|
1995
2026
|
);
|
|
2027
|
+
profilePublished = true;
|
|
2028
|
+
logger.debug({ event: "publish_ack", kind: 0 }, "profile published");
|
|
1996
2029
|
} catch (e) {
|
|
1997
2030
|
console.warn(` ! Failed to publish profile: ${e.message}`);
|
|
2031
|
+
logger.warn({ event: "publish_failed", kind: 0, error: e.message }, "profile publish failed");
|
|
1998
2032
|
}
|
|
1999
2033
|
const kinds = [jobRequestKind(DEFAULT_KIND_OFFSET)];
|
|
2000
2034
|
function buildCard(skill) {
|
|
@@ -2011,11 +2045,21 @@ image: ${url}`);
|
|
|
2011
2045
|
} : void 0
|
|
2012
2046
|
};
|
|
2013
2047
|
}
|
|
2048
|
+
let cardsPublished = 0;
|
|
2014
2049
|
for (const skill of allSkills) {
|
|
2015
2050
|
try {
|
|
2016
2051
|
await client.discovery.publishCapability(identity, buildCard(skill), kinds);
|
|
2052
|
+
cardsPublished += 1;
|
|
2053
|
+
logger.debug(
|
|
2054
|
+
{ event: "publish_ack", kind: 31990, skill: skill.name },
|
|
2055
|
+
"capability card published"
|
|
2056
|
+
);
|
|
2017
2057
|
} catch (e) {
|
|
2018
2058
|
console.warn(` ! Failed to publish "${skill.name}": ${e.message}`);
|
|
2059
|
+
logger.warn(
|
|
2060
|
+
{ event: "publish_failed", kind: 31990, skill: skill.name, error: e.message },
|
|
2061
|
+
"capability publish failed"
|
|
2062
|
+
);
|
|
2019
2063
|
}
|
|
2020
2064
|
}
|
|
2021
2065
|
try {
|
|
@@ -2042,6 +2086,12 @@ image: ${url}`);
|
|
|
2042
2086
|
} catch {
|
|
2043
2087
|
}
|
|
2044
2088
|
console.log(" Connected.\n");
|
|
2089
|
+
if (verbose) {
|
|
2090
|
+
console.log(
|
|
2091
|
+
` [debug] Published: profile=${profilePublished ? "ok" : "FAILED"} + ${cardsPublished}/${allSkills.length} capability cards (kind:31990)`
|
|
2092
|
+
);
|
|
2093
|
+
console.log(` [debug] Relays: ${relays.length} configured (${relays.join(", ")})`);
|
|
2094
|
+
}
|
|
2045
2095
|
const onPing = (senderPubkey, nonce) => {
|
|
2046
2096
|
client.ping.sendPong(identity, senderPubkey, nonce).catch(() => {
|
|
2047
2097
|
});
|
|
@@ -2052,12 +2102,15 @@ image: ${url}`);
|
|
|
2052
2102
|
heartbeatTimer = setInterval(async () => {
|
|
2053
2103
|
try {
|
|
2054
2104
|
await client.discovery.publishCapability(identity, heartbeatCard, kinds);
|
|
2055
|
-
|
|
2105
|
+
logger.debug({ event: "heartbeat_ack", skill: heartbeatCard.name }, "heartbeat ok");
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2108
|
+
logger.warn({ event: "heartbeat_failed", error: message }, "heartbeat publish failed");
|
|
2056
2109
|
}
|
|
2057
2110
|
}, HEARTBEAT_INTERVAL_MS);
|
|
2058
2111
|
}
|
|
2059
2112
|
const transport = new NostrTransport(client, identity, [DEFAULT_KIND_OFFSET]);
|
|
2060
|
-
const ledger = new JobLedger(
|
|
2113
|
+
const ledger = new JobLedger(paths.jobs);
|
|
2061
2114
|
const runtimeConfig = {
|
|
2062
2115
|
paymentTimeoutSecs: DEFAULTS.PAYMENT_EXPIRY_SECS,
|
|
2063
2116
|
maxConcurrentJobs: MAX_CONCURRENT_JOBS,
|
|
@@ -2066,26 +2119,49 @@ image: ${url}`);
|
|
|
2066
2119
|
network: walletNetwork,
|
|
2067
2120
|
solanaAddress
|
|
2068
2121
|
};
|
|
2069
|
-
const
|
|
2122
|
+
const rpcUrlForLog = stripRpcSecrets(process.env.SOLANA_RPC_URL ?? getRpcUrl());
|
|
2123
|
+
logger.debug(
|
|
2124
|
+
{
|
|
2125
|
+
event: "config_resolved",
|
|
2126
|
+
agent: agentName,
|
|
2127
|
+
source: loaded.source,
|
|
2128
|
+
network: walletNetwork,
|
|
2129
|
+
relays,
|
|
2130
|
+
solanaAddress,
|
|
2131
|
+
rpcUrl: rpcUrlForLog
|
|
2132
|
+
},
|
|
2133
|
+
"config resolved"
|
|
2134
|
+
);
|
|
2135
|
+
const diagLog = (msg) => {
|
|
2136
|
+
logWithIndent(msg);
|
|
2137
|
+
logger.info({ event: "runtime_diag" }, msg);
|
|
2138
|
+
};
|
|
2070
2139
|
const watchdog = startWatchdog({
|
|
2071
2140
|
client,
|
|
2072
2141
|
identity,
|
|
2073
2142
|
transport,
|
|
2074
2143
|
onPing,
|
|
2075
|
-
log:
|
|
2144
|
+
log: diagLog,
|
|
2145
|
+
logger
|
|
2076
2146
|
});
|
|
2077
2147
|
const runtime = new AgentRuntime(transport, registry, skillCtx, runtimeConfig, ledger, {
|
|
2078
2148
|
onJobReceived: (job) => {
|
|
2079
2149
|
const cap = job.tags.find((t) => t !== "elisym") ?? "unknown";
|
|
2080
|
-
|
|
2150
|
+
process.stdout.write(` [job] ${job.jobId.slice(0, 16)} | cap=${cap}
|
|
2151
|
+
`);
|
|
2152
|
+
logger.info({ event: "job_received", jobId: job.jobId, capability: cap });
|
|
2081
2153
|
},
|
|
2082
2154
|
onJobCompleted: (jobId) => {
|
|
2083
|
-
|
|
2155
|
+
process.stdout.write(` [job] ${jobId.slice(0, 16)} | delivered
|
|
2156
|
+
`);
|
|
2157
|
+
logger.info({ event: "job_delivered", jobId });
|
|
2084
2158
|
},
|
|
2085
2159
|
onJobError: (jobId, error) => {
|
|
2086
|
-
|
|
2160
|
+
process.stderr.write(` [job] ${jobId.slice(0, 16)} | error: ${error}
|
|
2161
|
+
`);
|
|
2162
|
+
logger.error({ event: "job_error", jobId, error });
|
|
2087
2163
|
},
|
|
2088
|
-
onLog:
|
|
2164
|
+
onLog: diagLog,
|
|
2089
2165
|
onStop: () => {
|
|
2090
2166
|
watchdog.stop();
|
|
2091
2167
|
if (heartbeatTimer) {
|
|
@@ -2096,15 +2172,100 @@ image: ${url}`);
|
|
|
2096
2172
|
console.log(" * Running. Press Ctrl+C to stop.\n");
|
|
2097
2173
|
await runtime.run();
|
|
2098
2174
|
}
|
|
2175
|
+
function stripRpcSecrets(raw) {
|
|
2176
|
+
try {
|
|
2177
|
+
const parsed = new URL(raw);
|
|
2178
|
+
parsed.username = "";
|
|
2179
|
+
parsed.password = "";
|
|
2180
|
+
const marker = parsed.search.length > 0 ? "?***" : "";
|
|
2181
|
+
parsed.search = "";
|
|
2182
|
+
return `${parsed.toString()}${marker}`;
|
|
2183
|
+
} catch {
|
|
2184
|
+
return "[unparseable RPC URL]";
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
async function resolveMediaField(value, agentDir, cache, media, identity, onCacheUpdate) {
|
|
2188
|
+
if (!value) {
|
|
2189
|
+
return void 0;
|
|
2190
|
+
}
|
|
2191
|
+
if (isRemoteUrl(value)) {
|
|
2192
|
+
return value;
|
|
2193
|
+
}
|
|
2194
|
+
const absPath = resolveInsideAgentDir(value, agentDir);
|
|
2195
|
+
if (!absPath) {
|
|
2196
|
+
console.warn(` ! Skipping media field "${value}": path must stay inside the agent directory.`);
|
|
2197
|
+
return void 0;
|
|
2198
|
+
}
|
|
2199
|
+
return uploadOrReuse(value, absPath, cache, media, identity, () => onCacheUpdate(true));
|
|
2200
|
+
}
|
|
2201
|
+
function resolveInsideAgentDir(value, agentDir) {
|
|
2202
|
+
const agentRoot = resolve(agentDir);
|
|
2203
|
+
const candidate = resolve(agentRoot, value);
|
|
2204
|
+
const rel = relative(agentRoot, candidate);
|
|
2205
|
+
if (rel === "" || rel.startsWith("..") || rel.includes(`..${sep}`)) {
|
|
2206
|
+
return null;
|
|
2207
|
+
}
|
|
2208
|
+
return candidate;
|
|
2209
|
+
}
|
|
2210
|
+
async function uploadOrReuse(cacheKey, absPath, cache, media, identity, onCacheUpdate) {
|
|
2211
|
+
try {
|
|
2212
|
+
const cached = await lookupCachedUrl(cache, cacheKey, absPath);
|
|
2213
|
+
if (cached) {
|
|
2214
|
+
return cached;
|
|
2215
|
+
}
|
|
2216
|
+
console.log(` Uploading ${basename(absPath)}...`);
|
|
2217
|
+
const data = readFileSync(absPath);
|
|
2218
|
+
const sha256 = createHash("sha256").update(data).digest("hex");
|
|
2219
|
+
const blob = new Blob([data]);
|
|
2220
|
+
const url = await media.upload(identity, blob, basename(absPath));
|
|
2221
|
+
cache[cacheKey] = newCacheEntry(url, sha256);
|
|
2222
|
+
onCacheUpdate();
|
|
2223
|
+
console.log(` Uploaded: ${url}`);
|
|
2224
|
+
return url;
|
|
2225
|
+
} catch (e) {
|
|
2226
|
+
console.warn(` ! Failed to upload ${basename(absPath)}: ${e.message}`);
|
|
2227
|
+
return void 0;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
function isRemoteUrl(value) {
|
|
2231
|
+
return /^https?:\/\//i.test(value);
|
|
2232
|
+
}
|
|
2099
2233
|
var MAX_PASSPHRASE_ATTEMPTS = 3;
|
|
2100
|
-
async function
|
|
2234
|
+
async function resolveStartAgentName(nameArg, cwd) {
|
|
2235
|
+
if (nameArg) {
|
|
2236
|
+
return nameArg;
|
|
2237
|
+
}
|
|
2238
|
+
const agents = await listAgents(cwd);
|
|
2239
|
+
if (agents.length === 0) {
|
|
2240
|
+
console.log("No agents configured. Run `elisym init` first.");
|
|
2241
|
+
process.exit(1);
|
|
2242
|
+
}
|
|
2243
|
+
const { default: inquirer } = await import('inquirer');
|
|
2244
|
+
const choices = [
|
|
2245
|
+
...agents.map((agent) => ({
|
|
2246
|
+
name: `${agent.name} (${agent.source}${agent.shadowsGlobal ? " - shadows global" : ""})`,
|
|
2247
|
+
value: agent.name
|
|
2248
|
+
})),
|
|
2249
|
+
{ name: "+ Create new agent", value: "__new__" }
|
|
2250
|
+
];
|
|
2251
|
+
const { selected } = await inquirer.prompt([
|
|
2252
|
+
{ type: "list", name: "selected", message: "Select agent to start", choices }
|
|
2253
|
+
]);
|
|
2254
|
+
if (selected === "__new__") {
|
|
2255
|
+
const { cmdInit: cmdInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2256
|
+
await cmdInit2();
|
|
2257
|
+
return void 0;
|
|
2258
|
+
}
|
|
2259
|
+
return selected;
|
|
2260
|
+
}
|
|
2261
|
+
async function loadAgentWithPrompt(name, cwd) {
|
|
2101
2262
|
const envPassphrase = process.env.ELISYM_PASSPHRASE;
|
|
2102
2263
|
try {
|
|
2103
|
-
return
|
|
2264
|
+
return await loadAgent(name, cwd, envPassphrase);
|
|
2104
2265
|
} catch (e) {
|
|
2105
|
-
const
|
|
2266
|
+
const isEncrypted3 = /encrypted secrets/i.test(e?.message ?? "");
|
|
2106
2267
|
const isWrongPassphrase = /Decryption failed/i.test(e?.message ?? "");
|
|
2107
|
-
if (!
|
|
2268
|
+
if (!isEncrypted3 && !isWrongPassphrase) {
|
|
2108
2269
|
throw e;
|
|
2109
2270
|
}
|
|
2110
2271
|
if (!process.stdin.isTTY) {
|
|
@@ -2122,7 +2283,7 @@ async function loadConfigWithPrompt(name) {
|
|
|
2122
2283
|
}
|
|
2123
2284
|
]);
|
|
2124
2285
|
try {
|
|
2125
|
-
return
|
|
2286
|
+
return await loadAgent(name, cwd, passphrase);
|
|
2126
2287
|
} catch (e) {
|
|
2127
2288
|
if (!/Decryption failed/i.test(e?.message ?? "")) {
|
|
2128
2289
|
throw e;
|
|
@@ -2137,12 +2298,10 @@ async function loadConfigWithPrompt(name) {
|
|
|
2137
2298
|
}
|
|
2138
2299
|
throw new Error("Unreachable");
|
|
2139
2300
|
}
|
|
2140
|
-
|
|
2141
|
-
// src/commands/wallet.ts
|
|
2142
|
-
init_config();
|
|
2143
2301
|
async function cmdWallet(name) {
|
|
2302
|
+
const cwd = process.cwd();
|
|
2144
2303
|
if (!name) {
|
|
2145
|
-
const agents = listAgents();
|
|
2304
|
+
const agents = await listAgents(cwd);
|
|
2146
2305
|
if (agents.length === 0) {
|
|
2147
2306
|
console.error("No agents found.");
|
|
2148
2307
|
process.exit(1);
|
|
@@ -2153,33 +2312,32 @@ async function cmdWallet(name) {
|
|
|
2153
2312
|
type: "list",
|
|
2154
2313
|
name: "selected",
|
|
2155
2314
|
message: "Select agent:",
|
|
2156
|
-
choices: agents
|
|
2315
|
+
choices: agents.map((agent) => ({
|
|
2316
|
+
name: `${agent.name} (${agent.source})`,
|
|
2317
|
+
value: agent.name
|
|
2318
|
+
}))
|
|
2157
2319
|
}
|
|
2158
2320
|
]);
|
|
2159
2321
|
name = selected;
|
|
2160
2322
|
}
|
|
2161
2323
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
2162
|
-
const
|
|
2163
|
-
const solPayment =
|
|
2324
|
+
const loaded = await loadAgent(name, cwd, passphrase);
|
|
2325
|
+
const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
2164
2326
|
if (!solPayment?.address) {
|
|
2165
2327
|
console.error("Solana address not configured for this agent.");
|
|
2166
2328
|
process.exit(1);
|
|
2167
2329
|
}
|
|
2168
|
-
const
|
|
2169
|
-
const rpcUrl = getRpcUrl();
|
|
2330
|
+
const rpcUrl = getRpcUrl(solPayment.network);
|
|
2170
2331
|
const rpc = createSolanaRpc(rpcUrl);
|
|
2171
2332
|
const walletAddress = address(solPayment.address);
|
|
2172
2333
|
const { value: balance } = await rpc.getBalance(walletAddress).send();
|
|
2173
2334
|
console.log(`
|
|
2174
2335
|
Agent: ${name}`);
|
|
2175
|
-
console.log(` Network: ${network}`);
|
|
2336
|
+
console.log(` Network: ${solPayment.network}`);
|
|
2176
2337
|
console.log(` Address: ${solPayment.address}`);
|
|
2177
2338
|
console.log(` Balance: ${formatSol(Number(balance))} (${balance} lamports)
|
|
2178
2339
|
`);
|
|
2179
2340
|
}
|
|
2180
|
-
|
|
2181
|
-
// src/index.ts
|
|
2182
|
-
init_config();
|
|
2183
2341
|
function readPackageVersion() {
|
|
2184
2342
|
try {
|
|
2185
2343
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2193,50 +2351,57 @@ var PACKAGE_VERSION = readPackageVersion();
|
|
|
2193
2351
|
|
|
2194
2352
|
// src/index.ts
|
|
2195
2353
|
process.removeAllListeners("warning");
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
program.command("profile [name]").description("Edit agent profile, wallet, and LLM settings").action(cmdProfile);
|
|
2199
|
-
program.command("start [name]").description("Start agent in provider mode").action(cmdStart);
|
|
2200
|
-
program.command("list").description("List all agents").action(() => {
|
|
2201
|
-
const agents = listAgents();
|
|
2202
|
-
if (agents.length === 0) {
|
|
2203
|
-
console.log("No agents found. Run `elisym init` to create one.");
|
|
2204
|
-
return;
|
|
2205
|
-
}
|
|
2206
|
-
console.log("\nAgents:");
|
|
2207
|
-
for (const name of agents) {
|
|
2354
|
+
function safe(fn) {
|
|
2355
|
+
return async (...args) => {
|
|
2208
2356
|
try {
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
const secretBytes = Buffer.from(secretKey, "hex");
|
|
2214
|
-
npub = secretBytes.length === 32 ? nip19.npubEncode(getPublicKey(secretBytes)) : "(invalid key)";
|
|
2215
|
-
}
|
|
2216
|
-
const solAddr = config.payments?.[0]?.address ? ` | Solana: ${config.payments[0].address}` : "";
|
|
2217
|
-
console.log(` ${name} | ${npub}${solAddr}`);
|
|
2218
|
-
} catch {
|
|
2219
|
-
console.log(` ${name} (error loading config)`);
|
|
2357
|
+
await fn(...args);
|
|
2358
|
+
} catch (e) {
|
|
2359
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
2360
|
+
process.exit(1);
|
|
2220
2361
|
}
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
program.command("
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
var program = new Command().name("elisym").description("CLI agent runner for the elisym network").version(PACKAGE_VERSION);
|
|
2365
|
+
program.command("init [name]").description("Create a new agent").option("-c, --config <path>", "Load fields from an elisym.yaml template (non-interactive)").option("--local", "Create in project <project>/.elisym/<name>/ (default: ~/.elisym/<name>/)").action(
|
|
2366
|
+
safe(async (name, options) => {
|
|
2367
|
+
await cmdInit(name, options);
|
|
2368
|
+
})
|
|
2369
|
+
);
|
|
2370
|
+
program.command("profile [name]").description("Edit agent profile, wallet, and LLM settings").action(safe(cmdProfile));
|
|
2371
|
+
program.command("start [name]").description("Start agent in provider mode").option(
|
|
2372
|
+
"-v, --verbose",
|
|
2373
|
+
"Enable debug logging (relay lifecycle, publish acks, subscription EOSE). Also togglable via ELISYM_DEBUG=1 or LOG_LEVEL=debug."
|
|
2374
|
+
).action(
|
|
2375
|
+
safe(async (name, options) => {
|
|
2376
|
+
await cmdStart(name, options);
|
|
2377
|
+
})
|
|
2378
|
+
);
|
|
2379
|
+
program.command("list").description("List all agents (project-local and home-global)").action(
|
|
2380
|
+
safe(async () => {
|
|
2381
|
+
const cwd = process.cwd();
|
|
2382
|
+
const agents = await listAgents(cwd);
|
|
2383
|
+
if (agents.length === 0) {
|
|
2384
|
+
console.log("No agents found. Run `elisym init` to create one.");
|
|
2385
|
+
return;
|
|
2233
2386
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2387
|
+
console.log("\nAgents:");
|
|
2388
|
+
for (const agent of agents) {
|
|
2389
|
+
try {
|
|
2390
|
+
const loaded = await loadAgent(agent.name, cwd);
|
|
2391
|
+
const identity = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
2392
|
+
const npub = nip19.npubEncode(identity.publicKey);
|
|
2393
|
+
const solAddr = loaded.yaml.payments[0]?.address ? ` | Solana: ${loaded.yaml.payments[0].address}` : "";
|
|
2394
|
+
const shadow = agent.shadowsGlobal ? " [shadows global]" : "";
|
|
2395
|
+
console.log(` ${agent.name} (${agent.source})${shadow} | ${npub}${solAddr}`);
|
|
2396
|
+
} catch (e) {
|
|
2397
|
+
const hint = /encrypted secrets/i.test(e?.message ?? "") ? " (encrypted)" : "";
|
|
2398
|
+
console.log(` ${agent.name} (${agent.source})${hint}`);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
console.log();
|
|
2402
|
+
})
|
|
2403
|
+
);
|
|
2404
|
+
program.command("wallet [name]").description("Show wallet balance").action(safe(cmdWallet));
|
|
2240
2405
|
program.parse();
|
|
2241
2406
|
//# sourceMappingURL=index.js.map
|
|
2242
2407
|
//# sourceMappingURL=index.js.map
|