@elisym/cli 0.3.1 → 0.4.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 +52 -33
- package/dist/index.js +491 -434
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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, 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';
|
|
10
12
|
import pLimit from 'p-limit';
|
|
11
|
-
import YAML from 'yaml';
|
|
12
13
|
import { spawn } from 'node:child_process';
|
|
13
14
|
import { StringDecoder } from 'node:string_decoder';
|
|
14
15
|
import { fileURLToPath } from 'node:url';
|
|
@@ -22,69 +23,6 @@ var __export = (target, all) => {
|
|
|
22
23
|
for (var name in all)
|
|
23
24
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
25
|
};
|
|
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
26
|
|
|
89
27
|
// src/commands/init.ts
|
|
90
28
|
var init_exports = {};
|
|
@@ -92,20 +30,6 @@ __export(init_exports, {
|
|
|
92
30
|
cmdInit: () => cmdInit,
|
|
93
31
|
fetchModels: () => fetchModels
|
|
94
32
|
});
|
|
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
33
|
async function fetchModels(provider, apiKey) {
|
|
110
34
|
try {
|
|
111
35
|
if (provider === "anthropic") {
|
|
@@ -138,31 +62,30 @@ async function fetchModels(provider, apiKey) {
|
|
|
138
62
|
return FALLBACK_MODELS[provider] ?? ["gpt-4o"];
|
|
139
63
|
}
|
|
140
64
|
}
|
|
141
|
-
|
|
65
|
+
function pickTarget(options) {
|
|
66
|
+
return options.local ? "project" : "home";
|
|
67
|
+
}
|
|
68
|
+
async function cmdInit(nameArg, options = {}) {
|
|
142
69
|
const { default: inquirer } = await import('inquirer');
|
|
70
|
+
const cwd = process.cwd();
|
|
143
71
|
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)) {
|
|
72
|
+
let template;
|
|
73
|
+
if (options.config) {
|
|
74
|
+
const configPath = resolve(cwd, options.config);
|
|
75
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
76
|
+
template = ElisymYamlSchema.parse(YAML.parse(raw) ?? {});
|
|
77
|
+
console.log(` Loaded template from ${configPath}
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
const agentName = await resolveAgentName(nameArg, inquirer);
|
|
81
|
+
const target = pickTarget(options);
|
|
82
|
+
const sameLocation = target === "home" ? resolveInHome(agentName) : resolveInProject(agentName, cwd);
|
|
83
|
+
if (sameLocation) {
|
|
161
84
|
const { overwrite } = await inquirer.prompt([
|
|
162
85
|
{
|
|
163
86
|
type: "confirm",
|
|
164
87
|
name: "overwrite",
|
|
165
|
-
message: `Agent "${
|
|
88
|
+
message: `Agent "${agentName}" already exists at ${sameLocation}. Overwrite secrets?`,
|
|
166
89
|
default: false
|
|
167
90
|
}
|
|
168
91
|
]);
|
|
@@ -170,7 +93,133 @@ async function cmdInit() {
|
|
|
170
93
|
console.log("Aborted.");
|
|
171
94
|
return;
|
|
172
95
|
}
|
|
96
|
+
} else if (target === "project" && resolveInHome(agentName)) {
|
|
97
|
+
const { shadow } = await inquirer.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: "confirm",
|
|
100
|
+
name: "shadow",
|
|
101
|
+
message: `A global agent "${agentName}" exists in ~/.elisym/${agentName}/. Create a project-local shadow?`,
|
|
102
|
+
default: true
|
|
103
|
+
}
|
|
104
|
+
]);
|
|
105
|
+
if (!shadow) {
|
|
106
|
+
console.log("Aborted.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
} else if (target === "home" && resolveInProject(agentName, cwd)) {
|
|
110
|
+
const { proceed } = await inquirer.prompt([
|
|
111
|
+
{
|
|
112
|
+
type: "confirm",
|
|
113
|
+
name: "proceed",
|
|
114
|
+
message: `A project-local agent "${agentName}" exists. Create a global agent with the same name?`,
|
|
115
|
+
default: true
|
|
116
|
+
}
|
|
117
|
+
]);
|
|
118
|
+
if (!proceed) {
|
|
119
|
+
console.log("Aborted.");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
173
122
|
}
|
|
123
|
+
let yaml;
|
|
124
|
+
let promptedApiKey;
|
|
125
|
+
if (template) {
|
|
126
|
+
yaml = template;
|
|
127
|
+
} else {
|
|
128
|
+
const result = await promptYaml(inquirer);
|
|
129
|
+
yaml = result.yaml;
|
|
130
|
+
promptedApiKey = result.apiKey;
|
|
131
|
+
}
|
|
132
|
+
let llmApiKey;
|
|
133
|
+
if (yaml.llm) {
|
|
134
|
+
const envKey = yaml.llm.provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
|
|
135
|
+
if (envKey) {
|
|
136
|
+
llmApiKey = envKey;
|
|
137
|
+
console.log(
|
|
138
|
+
` Using ${yaml.llm.provider === "anthropic" ? "ANTHROPIC" : "OPENAI"}_API_KEY from environment.`
|
|
139
|
+
);
|
|
140
|
+
} else if (promptedApiKey) {
|
|
141
|
+
llmApiKey = promptedApiKey;
|
|
142
|
+
} else {
|
|
143
|
+
const { apiKey } = await inquirer.prompt([
|
|
144
|
+
{
|
|
145
|
+
type: "password",
|
|
146
|
+
name: "apiKey",
|
|
147
|
+
message: `${yaml.llm.provider === "anthropic" ? "Anthropic" : "OpenAI"} API key:`,
|
|
148
|
+
mask: "*"
|
|
149
|
+
}
|
|
150
|
+
]);
|
|
151
|
+
llmApiKey = apiKey || void 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const { passphrase } = await inquirer.prompt([
|
|
155
|
+
{
|
|
156
|
+
type: "password",
|
|
157
|
+
name: "passphrase",
|
|
158
|
+
message: "Passphrase to encrypt secrets (leave empty to skip):",
|
|
159
|
+
mask: "*"
|
|
160
|
+
}
|
|
161
|
+
]);
|
|
162
|
+
if (passphrase) {
|
|
163
|
+
const { confirmPassphrase } = await inquirer.prompt([
|
|
164
|
+
{
|
|
165
|
+
type: "password",
|
|
166
|
+
name: "confirmPassphrase",
|
|
167
|
+
message: "Confirm passphrase:",
|
|
168
|
+
mask: "*",
|
|
169
|
+
validate: (value) => value === passphrase || "Passphrases do not match"
|
|
170
|
+
}
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
const nostrSecretBytes = generateSecretKey();
|
|
174
|
+
const nostrPubkey = getPublicKey(nostrSecretBytes);
|
|
175
|
+
const nostrSecretHex = Buffer.from(nostrSecretBytes).toString("hex");
|
|
176
|
+
const created = await createAgentDir({ target, name: agentName, cwd });
|
|
177
|
+
await writeYaml(created.dir, yaml);
|
|
178
|
+
await writeSecrets(
|
|
179
|
+
created.dir,
|
|
180
|
+
{
|
|
181
|
+
nostr_secret_key: nostrSecretHex,
|
|
182
|
+
llm_api_key: llmApiKey
|
|
183
|
+
},
|
|
184
|
+
passphrase || void 0
|
|
185
|
+
);
|
|
186
|
+
const npub = nip19.npubEncode(nostrPubkey);
|
|
187
|
+
console.log(`
|
|
188
|
+
Agent "${agentName}" created (${target}).`);
|
|
189
|
+
console.log(` Location: ${created.dir}`);
|
|
190
|
+
console.log(` Nostr: ${npub}`);
|
|
191
|
+
if (yaml.payments[0]?.address) {
|
|
192
|
+
console.log(` Solana: ${yaml.payments[0].address}`);
|
|
193
|
+
}
|
|
194
|
+
if (passphrase) {
|
|
195
|
+
console.log(" Secrets encrypted with your passphrase.");
|
|
196
|
+
}
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
async function resolveAgentName(nameArg, inquirer) {
|
|
200
|
+
if (nameArg) {
|
|
201
|
+
validateAgentName(nameArg);
|
|
202
|
+
return nameArg;
|
|
203
|
+
}
|
|
204
|
+
const { inputName } = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: "input",
|
|
207
|
+
name: "inputName",
|
|
208
|
+
message: "Agent name:",
|
|
209
|
+
validate: (value) => {
|
|
210
|
+
try {
|
|
211
|
+
validateAgentName(value);
|
|
212
|
+
return true;
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return e.message;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
]);
|
|
219
|
+
validateAgentName(inputName);
|
|
220
|
+
return inputName;
|
|
221
|
+
}
|
|
222
|
+
async function promptYaml(inquirer) {
|
|
174
223
|
const { description } = await inquirer.prompt([
|
|
175
224
|
{
|
|
176
225
|
type: "input",
|
|
@@ -179,19 +228,27 @@ async function cmdInit() {
|
|
|
179
228
|
default: "An elisym AI agent"
|
|
180
229
|
}
|
|
181
230
|
]);
|
|
182
|
-
const {
|
|
231
|
+
const { displayName } = await inquirer.prompt([
|
|
232
|
+
{
|
|
233
|
+
type: "input",
|
|
234
|
+
name: "displayName",
|
|
235
|
+
message: "Display name (optional, for UI):",
|
|
236
|
+
default: ""
|
|
237
|
+
}
|
|
238
|
+
]);
|
|
239
|
+
const { picture } = await inquirer.prompt([
|
|
183
240
|
{
|
|
184
241
|
type: "input",
|
|
185
|
-
name: "
|
|
186
|
-
message: "Avatar
|
|
242
|
+
name: "picture",
|
|
243
|
+
message: "Avatar file (relative to agent dir, e.g. ./avatar.png) or URL:",
|
|
187
244
|
default: ""
|
|
188
245
|
}
|
|
189
246
|
]);
|
|
190
|
-
const {
|
|
247
|
+
const { banner } = await inquirer.prompt([
|
|
191
248
|
{
|
|
192
249
|
type: "input",
|
|
193
|
-
name: "
|
|
194
|
-
message: "Banner
|
|
250
|
+
name: "banner",
|
|
251
|
+
message: "Banner file (relative to agent dir, e.g. ./header.png) or URL:",
|
|
195
252
|
default: ""
|
|
196
253
|
}
|
|
197
254
|
]);
|
|
@@ -201,27 +258,14 @@ async function cmdInit() {
|
|
|
201
258
|
name: "solanaAddress",
|
|
202
259
|
message: "Solana address for receiving payments (leave empty to skip):",
|
|
203
260
|
default: "",
|
|
204
|
-
validate: (
|
|
205
|
-
if (!
|
|
261
|
+
validate: (value) => {
|
|
262
|
+
if (!value) {
|
|
206
263
|
return true;
|
|
207
264
|
}
|
|
208
|
-
return isAddress(
|
|
265
|
+
return isAddress(value) || "Invalid Solana address";
|
|
209
266
|
}
|
|
210
267
|
}
|
|
211
268
|
]);
|
|
212
|
-
const { network } = await inquirer.prompt([
|
|
213
|
-
{
|
|
214
|
-
type: "list",
|
|
215
|
-
name: "network",
|
|
216
|
-
message: "Solana network:",
|
|
217
|
-
choices: [
|
|
218
|
-
{ name: "devnet", value: "devnet" },
|
|
219
|
-
{ name: "testnet", value: "testnet" },
|
|
220
|
-
{ name: "mainnet", value: "mainnet" }
|
|
221
|
-
],
|
|
222
|
-
default: "devnet"
|
|
223
|
-
}
|
|
224
|
-
]);
|
|
225
269
|
const { llmProvider } = await inquirer.prompt([
|
|
226
270
|
{
|
|
227
271
|
type: "list",
|
|
@@ -233,16 +277,21 @@ async function cmdInit() {
|
|
|
233
277
|
]
|
|
234
278
|
}
|
|
235
279
|
]);
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
280
|
+
const envKey = llmProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
|
|
281
|
+
let apiKey = envKey;
|
|
282
|
+
if (!apiKey) {
|
|
283
|
+
const { promptedKey } = await inquirer.prompt([
|
|
284
|
+
{
|
|
285
|
+
type: "password",
|
|
286
|
+
name: "promptedKey",
|
|
287
|
+
message: `${llmProvider === "anthropic" ? "Anthropic" : "OpenAI"} API key:`,
|
|
288
|
+
mask: "*"
|
|
289
|
+
}
|
|
290
|
+
]);
|
|
291
|
+
apiKey = promptedKey || void 0;
|
|
292
|
+
}
|
|
244
293
|
console.log(" Fetching available models...");
|
|
245
|
-
const models = await fetchModels(llmProvider, apiKey);
|
|
294
|
+
const models = await fetchModels(llmProvider, apiKey ?? "");
|
|
246
295
|
const { model } = await inquirer.prompt([
|
|
247
296
|
{
|
|
248
297
|
type: "list",
|
|
@@ -259,77 +308,21 @@ async function cmdInit() {
|
|
|
259
308
|
default: 4096
|
|
260
309
|
}
|
|
261
310
|
]);
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
mask: "*"
|
|
268
|
-
}
|
|
269
|
-
]);
|
|
270
|
-
if (passphrase) {
|
|
271
|
-
const { confirmPassphrase } = await inquirer.prompt([
|
|
272
|
-
{
|
|
273
|
-
type: "password",
|
|
274
|
-
name: "confirmPassphrase",
|
|
275
|
-
message: "Confirm passphrase:",
|
|
276
|
-
mask: "*",
|
|
277
|
-
validate: (v) => v === passphrase ? true : "Passphrases do not match"
|
|
278
|
-
}
|
|
279
|
-
]);
|
|
280
|
-
}
|
|
281
|
-
const nostrSecretKey = generateSecretKey();
|
|
282
|
-
const nostrPubkey = getPublicKey(nostrSecretKey);
|
|
283
|
-
const nostrSecretHex = Buffer.from(nostrSecretKey).toString("hex");
|
|
284
|
-
const identity = ElisymIdentity.fromHex(nostrSecretHex);
|
|
285
|
-
const media = new MediaService();
|
|
286
|
-
let picture = "";
|
|
287
|
-
let banner = "";
|
|
288
|
-
if (pictureInput || bannerInput) {
|
|
289
|
-
console.log();
|
|
290
|
-
if (pictureInput) {
|
|
291
|
-
picture = await resolveImage(pictureInput, identity, media);
|
|
292
|
-
}
|
|
293
|
-
if (bannerInput) {
|
|
294
|
-
banner = await resolveImage(bannerInput, identity, media);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
const protect = (secret) => passphrase ? encryptSecret(secret, passphrase) : secret;
|
|
298
|
-
const config = {
|
|
299
|
-
identity: {
|
|
300
|
-
secret_key: protect(nostrSecretHex),
|
|
301
|
-
name,
|
|
302
|
-
description,
|
|
303
|
-
picture: picture || void 0,
|
|
304
|
-
banner: banner || void 0
|
|
305
|
-
},
|
|
311
|
+
const yaml = ElisymYamlSchema.parse({
|
|
312
|
+
display_name: displayName || void 0,
|
|
313
|
+
description,
|
|
314
|
+
picture: picture || void 0,
|
|
315
|
+
banner: banner || void 0,
|
|
306
316
|
relays: [...RELAYS],
|
|
307
|
-
payments: solanaAddress ? [{ chain: "solana", network, address: solanaAddress }] :
|
|
308
|
-
llm: {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
max_tokens: maxTokens
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
saveConfig(config);
|
|
316
|
-
const npub = nip19.npubEncode(nostrPubkey);
|
|
317
|
-
console.log(`
|
|
318
|
-
Agent "${name}" created.`);
|
|
319
|
-
console.log(` Nostr: ${npub}`);
|
|
320
|
-
if (solanaAddress) {
|
|
321
|
-
console.log(` Solana: ${solanaAddress}`);
|
|
322
|
-
}
|
|
323
|
-
if (passphrase) {
|
|
324
|
-
console.log(" Secrets encrypted with your passphrase.");
|
|
325
|
-
}
|
|
326
|
-
console.log(` Config: ~/.elisym/agents/${name}/config.json
|
|
327
|
-
`);
|
|
317
|
+
payments: solanaAddress ? [{ chain: "solana", network: "devnet", address: solanaAddress }] : [],
|
|
318
|
+
llm: { provider: llmProvider, model, max_tokens: maxTokens },
|
|
319
|
+
security: {}
|
|
320
|
+
});
|
|
321
|
+
return { yaml, apiKey: envKey ? void 0 : apiKey };
|
|
328
322
|
}
|
|
329
323
|
var FALLBACK_MODELS;
|
|
330
324
|
var init_init = __esm({
|
|
331
325
|
"src/commands/init.ts"() {
|
|
332
|
-
init_config();
|
|
333
326
|
FALLBACK_MODELS = {
|
|
334
327
|
anthropic: ["claude-sonnet-4-6", "claude-haiku-4-5-20251001", "claude-opus-4-6"],
|
|
335
328
|
openai: ["gpt-4o", "gpt-4o-mini", "o3-mini"]
|
|
@@ -339,41 +332,32 @@ var init_init = __esm({
|
|
|
339
332
|
|
|
340
333
|
// src/index.ts
|
|
341
334
|
init_init();
|
|
342
|
-
|
|
343
|
-
// src/commands/profile.ts
|
|
344
|
-
init_config();
|
|
345
|
-
async function resolveImage2(input, identity, media) {
|
|
346
|
-
if (!input) {
|
|
347
|
-
return "";
|
|
348
|
-
}
|
|
349
|
-
if (existsSync(input)) {
|
|
350
|
-
console.log(` Uploading ${basename(input)}...`);
|
|
351
|
-
const data = readFileSync(input);
|
|
352
|
-
const blob = new Blob([data]);
|
|
353
|
-
const url = await media.upload(identity, blob, basename(input));
|
|
354
|
-
console.log(` Uploaded: ${url}`);
|
|
355
|
-
return url;
|
|
356
|
-
}
|
|
357
|
-
return input;
|
|
358
|
-
}
|
|
359
335
|
async function cmdProfile(name) {
|
|
360
336
|
const { default: inquirer } = await import('inquirer');
|
|
337
|
+
const cwd = process.cwd();
|
|
361
338
|
if (!name) {
|
|
362
|
-
const agents = listAgents();
|
|
339
|
+
const agents = await listAgents(cwd);
|
|
363
340
|
if (agents.length === 0) {
|
|
364
341
|
console.error("No agents found. Run `elisym init` first.");
|
|
365
342
|
process.exit(1);
|
|
366
343
|
}
|
|
367
344
|
const { selected } = await inquirer.prompt([
|
|
368
|
-
{
|
|
345
|
+
{
|
|
346
|
+
type: "list",
|
|
347
|
+
name: "selected",
|
|
348
|
+
message: "Select agent:",
|
|
349
|
+
choices: agents.map((agent) => ({
|
|
350
|
+
name: `${agent.name} (${agent.source})`,
|
|
351
|
+
value: agent.name
|
|
352
|
+
}))
|
|
353
|
+
}
|
|
369
354
|
]);
|
|
370
355
|
name = selected;
|
|
371
356
|
}
|
|
372
357
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
373
|
-
const
|
|
374
|
-
const identity = ElisymIdentity.fromHex(config.identity.secret_key);
|
|
358
|
+
const loaded = await loadAgent(name, cwd, passphrase);
|
|
375
359
|
console.log(`
|
|
376
|
-
Editing agent "${name}"
|
|
360
|
+
Editing agent "${name}" (${loaded.source})
|
|
377
361
|
`);
|
|
378
362
|
let done = false;
|
|
379
363
|
while (!done) {
|
|
@@ -383,13 +367,16 @@ async function cmdProfile(name) {
|
|
|
383
367
|
name: "section",
|
|
384
368
|
message: "What to edit?",
|
|
385
369
|
choices: [
|
|
386
|
-
{ name: `Profile (name: ${config.identity.name})`, value: "profile" },
|
|
387
370
|
{
|
|
388
|
-
name: `
|
|
371
|
+
name: `Profile (description: ${truncate(loaded.yaml.description ?? "")})`,
|
|
372
|
+
value: "profile"
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: `Wallet (${loaded.yaml.payments[0]?.address ?? "not configured"})`,
|
|
389
376
|
value: "wallet"
|
|
390
377
|
},
|
|
391
378
|
{
|
|
392
|
-
name: `LLM (${
|
|
379
|
+
name: `LLM (${loaded.yaml.llm?.provider ?? "not configured"} / ${loaded.yaml.llm?.model ?? "-"})`,
|
|
393
380
|
value: "llm"
|
|
394
381
|
},
|
|
395
382
|
{ name: "Done", value: "done" }
|
|
@@ -402,70 +389,64 @@ async function cmdProfile(name) {
|
|
|
402
389
|
}
|
|
403
390
|
if (section === "profile") {
|
|
404
391
|
const answers = await inquirer.prompt([
|
|
392
|
+
{
|
|
393
|
+
type: "input",
|
|
394
|
+
name: "displayName",
|
|
395
|
+
message: "Display name (for UI):",
|
|
396
|
+
default: loaded.yaml.display_name ?? ""
|
|
397
|
+
},
|
|
405
398
|
{
|
|
406
399
|
type: "input",
|
|
407
400
|
name: "description",
|
|
408
401
|
message: "Description:",
|
|
409
|
-
default:
|
|
402
|
+
default: loaded.yaml.description ?? ""
|
|
410
403
|
},
|
|
411
404
|
{
|
|
412
405
|
type: "input",
|
|
413
406
|
name: "picture",
|
|
414
|
-
message: "Avatar (
|
|
415
|
-
default:
|
|
407
|
+
message: "Avatar (relative path or URL, empty to clear):",
|
|
408
|
+
default: loaded.yaml.picture ?? ""
|
|
416
409
|
},
|
|
417
410
|
{
|
|
418
411
|
type: "input",
|
|
419
412
|
name: "banner",
|
|
420
|
-
message: "Banner (
|
|
421
|
-
default:
|
|
413
|
+
message: "Banner (relative path or URL, empty to clear):",
|
|
414
|
+
default: loaded.yaml.banner ?? ""
|
|
422
415
|
}
|
|
423
416
|
]);
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
} else {
|
|
434
|
-
config.identity.banner = void 0;
|
|
435
|
-
}
|
|
436
|
-
saveConfig(config);
|
|
417
|
+
const nextYaml = {
|
|
418
|
+
...loaded.yaml,
|
|
419
|
+
display_name: answers.displayName || void 0,
|
|
420
|
+
description: answers.description ?? "",
|
|
421
|
+
picture: answers.picture || void 0,
|
|
422
|
+
banner: answers.banner || void 0
|
|
423
|
+
};
|
|
424
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
425
|
+
loaded.yaml = nextYaml;
|
|
437
426
|
console.log(" Profile updated.\n");
|
|
438
427
|
}
|
|
439
428
|
if (section === "wallet") {
|
|
440
|
-
const
|
|
441
|
-
const currentNetwork = config.payments?.[0]?.network ?? "devnet";
|
|
429
|
+
const current = loaded.yaml.payments[0];
|
|
442
430
|
const answers = await inquirer.prompt([
|
|
443
431
|
{
|
|
444
432
|
type: "input",
|
|
445
433
|
name: "address",
|
|
446
|
-
message: "Solana address:",
|
|
447
|
-
default:
|
|
448
|
-
validate: (
|
|
449
|
-
if (!
|
|
434
|
+
message: "Solana address (empty to clear):",
|
|
435
|
+
default: current?.address ?? "",
|
|
436
|
+
validate: (value) => {
|
|
437
|
+
if (!value) {
|
|
450
438
|
return true;
|
|
451
439
|
}
|
|
452
|
-
return isAddress(
|
|
440
|
+
return isAddress(value) || "Invalid Solana address";
|
|
453
441
|
}
|
|
454
|
-
},
|
|
455
|
-
{
|
|
456
|
-
type: "list",
|
|
457
|
-
name: "network",
|
|
458
|
-
message: "Network:",
|
|
459
|
-
choices: ["devnet", "testnet", "mainnet"],
|
|
460
|
-
default: currentNetwork
|
|
461
442
|
}
|
|
462
443
|
]);
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
444
|
+
const nextYaml = {
|
|
445
|
+
...loaded.yaml,
|
|
446
|
+
payments: answers.address ? [{ chain: "solana", network: "devnet", address: answers.address }] : []
|
|
447
|
+
};
|
|
448
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
449
|
+
loaded.yaml = nextYaml;
|
|
469
450
|
console.log(" Wallet updated.\n");
|
|
470
451
|
}
|
|
471
452
|
if (section === "llm") {
|
|
@@ -478,33 +459,29 @@ async function cmdProfile(name) {
|
|
|
478
459
|
{ name: "Anthropic (Claude)", value: "anthropic" },
|
|
479
460
|
{ name: "OpenAI (GPT)", value: "openai" }
|
|
480
461
|
],
|
|
481
|
-
default:
|
|
462
|
+
default: loaded.yaml.llm?.provider ?? "anthropic"
|
|
482
463
|
}
|
|
483
464
|
]);
|
|
484
465
|
const { apiKey } = await inquirer.prompt([
|
|
485
466
|
{
|
|
486
467
|
type: "password",
|
|
487
468
|
name: "apiKey",
|
|
488
|
-
message:
|
|
469
|
+
message: "API key (leave empty to keep current):",
|
|
489
470
|
mask: "*"
|
|
490
471
|
}
|
|
491
472
|
]);
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
if (isEncrypted(plainKey) && passphrase) {
|
|
495
|
-
const { decryptSecret } = await import('@elisym/sdk/node');
|
|
496
|
-
plainKey = decryptSecret(plainKey, passphrase);
|
|
497
|
-
}
|
|
473
|
+
const currentKeyPlain = loaded.secrets.llm_api_key && !isEncrypted(loaded.secrets.llm_api_key) ? loaded.secrets.llm_api_key : "";
|
|
474
|
+
const probeKey = apiKey || currentKeyPlain;
|
|
498
475
|
console.log(" Fetching available models...");
|
|
499
476
|
const { fetchModels: fetchModels2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
500
|
-
const models = await fetchModels2(llmProvider,
|
|
477
|
+
const models = await fetchModels2(llmProvider, probeKey);
|
|
501
478
|
const { model } = await inquirer.prompt([
|
|
502
479
|
{
|
|
503
480
|
type: "list",
|
|
504
481
|
name: "model",
|
|
505
482
|
message: "Model:",
|
|
506
483
|
choices: models,
|
|
507
|
-
default:
|
|
484
|
+
default: loaded.yaml.llm?.model
|
|
508
485
|
}
|
|
509
486
|
]);
|
|
510
487
|
const { maxTokens } = await inquirer.prompt([
|
|
@@ -512,26 +489,29 @@ async function cmdProfile(name) {
|
|
|
512
489
|
type: "number",
|
|
513
490
|
name: "maxTokens",
|
|
514
491
|
message: "Max tokens:",
|
|
515
|
-
default:
|
|
492
|
+
default: loaded.yaml.llm?.max_tokens ?? 4096
|
|
516
493
|
}
|
|
517
494
|
]);
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
495
|
+
const nextLlm = { provider: llmProvider, model, max_tokens: maxTokens };
|
|
496
|
+
const nextYaml = { ...loaded.yaml, llm: nextLlm };
|
|
497
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
498
|
+
loaded.yaml = nextYaml;
|
|
499
|
+
if (apiKey) {
|
|
500
|
+
await writeSecrets(loaded.dir, { ...loaded.secrets, llm_api_key: apiKey }, passphrase);
|
|
501
|
+
loaded.secrets = { ...loaded.secrets, llm_api_key: apiKey };
|
|
502
|
+
}
|
|
526
503
|
console.log(" LLM updated.\n");
|
|
527
504
|
}
|
|
528
505
|
}
|
|
529
506
|
console.log(` Agent "${name}" saved.
|
|
530
507
|
`);
|
|
531
508
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
509
|
+
function truncate(value, max = 40) {
|
|
510
|
+
if (value.length <= max) {
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
return value.slice(0, max - 1) + "\u2026";
|
|
514
|
+
}
|
|
535
515
|
var HEARTBEAT_INTERVAL_MS = 10 * 60 * 1e3;
|
|
536
516
|
var MAX_CONCURRENT_JOBS = 10;
|
|
537
517
|
var RECOVERY_MAX_RETRIES = 5;
|
|
@@ -540,19 +520,12 @@ var WATCHDOG_PROBE_INTERVAL_MS = 5 * 60 * 1e3;
|
|
|
540
520
|
var WATCHDOG_PROBE_TIMEOUT_MS = 1e4;
|
|
541
521
|
var WATCHDOG_SELF_PING_INTERVAL_MS = 10 * 60 * 1e3;
|
|
542
522
|
var WATCHDOG_SELF_PING_TIMEOUT_MS = 15e3;
|
|
543
|
-
function getRpcUrl(
|
|
523
|
+
function getRpcUrl(_network) {
|
|
544
524
|
const envUrl = process.env.SOLANA_RPC_URL;
|
|
545
525
|
if (envUrl) {
|
|
546
526
|
return envUrl;
|
|
547
527
|
}
|
|
548
|
-
|
|
549
|
-
case "mainnet":
|
|
550
|
-
return "https://api.mainnet-beta.solana.com";
|
|
551
|
-
case "testnet":
|
|
552
|
-
return "https://api.testnet.solana.com";
|
|
553
|
-
default:
|
|
554
|
-
return "https://api.devnet.solana.com";
|
|
555
|
-
}
|
|
528
|
+
return "https://api.devnet.solana.com";
|
|
556
529
|
}
|
|
557
530
|
var VALID_TRANSITIONS = {
|
|
558
531
|
paid: ["executed", "failed"],
|
|
@@ -563,8 +536,13 @@ var VALID_TRANSITIONS = {
|
|
|
563
536
|
var JobLedger = class {
|
|
564
537
|
entries = /* @__PURE__ */ new Map();
|
|
565
538
|
path;
|
|
566
|
-
|
|
567
|
-
|
|
539
|
+
/**
|
|
540
|
+
* @param ledgerPath absolute path to the ledger file
|
|
541
|
+
* (typically `<agentDir>/.jobs.json`). Caller is responsible for
|
|
542
|
+
* resolving the agent directory via `@elisym/sdk/agent-store`.
|
|
543
|
+
*/
|
|
544
|
+
constructor(ledgerPath) {
|
|
545
|
+
this.path = ledgerPath;
|
|
568
546
|
this.load();
|
|
569
547
|
}
|
|
570
548
|
load() {
|
|
@@ -688,7 +666,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
688
666
|
if (!signal) {
|
|
689
667
|
return new Promise((r) => setTimeout(r, ms));
|
|
690
668
|
}
|
|
691
|
-
return new Promise((
|
|
669
|
+
return new Promise((resolve3, reject) => {
|
|
692
670
|
const cleanup = () => {
|
|
693
671
|
clearTimeout(timer);
|
|
694
672
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -699,7 +677,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
699
677
|
};
|
|
700
678
|
const timer = setTimeout(() => {
|
|
701
679
|
cleanup();
|
|
702
|
-
|
|
680
|
+
resolve3();
|
|
703
681
|
}, ms);
|
|
704
682
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
705
683
|
});
|
|
@@ -1000,8 +978,12 @@ var AgentRuntime = class {
|
|
|
1000
978
|
globalJobs = [];
|
|
1001
979
|
/** Fetch on-chain protocol config (fee, treasury). Always fetches fresh to avoid stale treasury. */
|
|
1002
980
|
async fetchProtocolConfig() {
|
|
1003
|
-
|
|
1004
|
-
|
|
981
|
+
if (this.config.network !== "devnet") {
|
|
982
|
+
throw new Error(
|
|
983
|
+
`Network "${this.config.network}" is not supported. Only "devnet" is available until the on-chain protocol program is deployed on mainnet.`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
const programId = getProtocolProgramId("devnet");
|
|
1005
987
|
const rpc = createSolanaRpc(getRpcUrl(this.config.network));
|
|
1006
988
|
const config = await getProtocolConfig(rpc, programId, { forceRefresh: true });
|
|
1007
989
|
return { feeBps: config.feeBps, treasury: config.treasury };
|
|
@@ -1062,8 +1044,8 @@ var AgentRuntime = class {
|
|
|
1062
1044
|
});
|
|
1063
1045
|
});
|
|
1064
1046
|
log("Agent runtime started. Listening for jobs...");
|
|
1065
|
-
await new Promise((
|
|
1066
|
-
this.abortController.signal.addEventListener("abort", () =>
|
|
1047
|
+
await new Promise((resolve3) => {
|
|
1048
|
+
this.abortController.signal.addEventListener("abort", () => resolve3(), { once: true });
|
|
1067
1049
|
process.on("SIGINT", () => {
|
|
1068
1050
|
log("Shutting down...");
|
|
1069
1051
|
this.stop();
|
|
@@ -1560,7 +1542,7 @@ var ScriptSkill = class {
|
|
|
1560
1542
|
throw new Error(`Max tool rounds (${this.maxToolRounds}) exceeded`);
|
|
1561
1543
|
}
|
|
1562
1544
|
runTool(toolDef, call, signal) {
|
|
1563
|
-
return new Promise((
|
|
1545
|
+
return new Promise((resolve3, _reject) => {
|
|
1564
1546
|
const args = [...toolDef.command];
|
|
1565
1547
|
const cmd = args.shift();
|
|
1566
1548
|
const params = toolDef.parameters ?? [];
|
|
@@ -1603,13 +1585,13 @@ var ScriptSkill = class {
|
|
|
1603
1585
|
});
|
|
1604
1586
|
child.on("close", (code) => {
|
|
1605
1587
|
if (code === 0) {
|
|
1606
|
-
|
|
1588
|
+
resolve3(stdout.trim());
|
|
1607
1589
|
} else {
|
|
1608
|
-
|
|
1590
|
+
resolve3(`Error (exit ${code}): ${stderr.trim() || stdout.trim()}`);
|
|
1609
1591
|
}
|
|
1610
1592
|
});
|
|
1611
1593
|
child.on("error", (err) => {
|
|
1612
|
-
|
|
1594
|
+
resolve3(`Error: ${err.message}`);
|
|
1613
1595
|
});
|
|
1614
1596
|
});
|
|
1615
1597
|
}
|
|
@@ -1875,37 +1857,25 @@ function startWatchdog(deps) {
|
|
|
1875
1857
|
}
|
|
1876
1858
|
|
|
1877
1859
|
// src/commands/start.ts
|
|
1878
|
-
async function cmdStart(
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
process.exit(1);
|
|
1884
|
-
}
|
|
1885
|
-
const { default: inquirer } = await import('inquirer');
|
|
1886
|
-
const choices = [...agents, { name: "+ Create new agent", value: "__new__" }];
|
|
1887
|
-
const { selected } = await inquirer.prompt([
|
|
1888
|
-
{ type: "list", name: "selected", message: "Select agent to start", choices }
|
|
1889
|
-
]);
|
|
1890
|
-
if (selected === "__new__") {
|
|
1891
|
-
const { cmdInit: cmdInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
1892
|
-
await cmdInit2();
|
|
1893
|
-
return;
|
|
1894
|
-
}
|
|
1895
|
-
name = selected;
|
|
1860
|
+
async function cmdStart(nameArg) {
|
|
1861
|
+
const cwd = process.cwd();
|
|
1862
|
+
const agentName = await resolveStartAgentName(nameArg, cwd);
|
|
1863
|
+
if (!agentName) {
|
|
1864
|
+
return;
|
|
1896
1865
|
}
|
|
1897
|
-
const
|
|
1866
|
+
const loaded = await loadAgentWithPrompt(agentName, cwd);
|
|
1898
1867
|
console.log(`
|
|
1899
|
-
Starting agent ${
|
|
1868
|
+
Starting agent ${agentName} (${loaded.source})...
|
|
1900
1869
|
`);
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
}
|
|
1870
|
+
if (loaded.shadowsGlobal) {
|
|
1871
|
+
console.log(
|
|
1872
|
+
` ! Using project-local ${agentName} (shadows global agent in ~/.elisym/${agentName}/)
|
|
1873
|
+
`
|
|
1874
|
+
);
|
|
1907
1875
|
}
|
|
1908
|
-
const
|
|
1876
|
+
const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
1877
|
+
const solanaAddress = solPayment?.address;
|
|
1878
|
+
const walletNetwork = solPayment?.network ?? "devnet";
|
|
1909
1879
|
if (solanaAddress) {
|
|
1910
1880
|
try {
|
|
1911
1881
|
const rpcUrl = getRpcUrl(walletNetwork);
|
|
@@ -1921,18 +1891,7 @@ async function cmdStart(name) {
|
|
|
1921
1891
|
}
|
|
1922
1892
|
console.log(` Balance ${formatSol(balance)} (${balance} lamports)`);
|
|
1923
1893
|
if (balance === 0) {
|
|
1924
|
-
|
|
1925
|
-
console.log(
|
|
1926
|
-
" ! Warning: wallet is empty. First incoming payment needs rent-exempt SOL (~0.00089 SOL)."
|
|
1927
|
-
);
|
|
1928
|
-
} else {
|
|
1929
|
-
console.log(" ! Wallet is empty. Get devnet SOL: https://faucet.solana.com");
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
if (walletNetwork === "mainnet" && !process.env.SOLANA_RPC_URL) {
|
|
1933
|
-
console.log(
|
|
1934
|
-
" ! Warning: using public Solana RPC for mainnet. Set SOLANA_RPC_URL for reliable operation."
|
|
1935
|
-
);
|
|
1894
|
+
console.log(" ! Wallet is empty. Get devnet SOL: https://faucet.solana.com");
|
|
1936
1895
|
}
|
|
1937
1896
|
console.log();
|
|
1938
1897
|
} catch (e) {
|
|
@@ -1940,17 +1899,26 @@ async function cmdStart(name) {
|
|
|
1940
1899
|
`);
|
|
1941
1900
|
}
|
|
1942
1901
|
}
|
|
1943
|
-
if (!
|
|
1902
|
+
if (!loaded.yaml.llm) {
|
|
1944
1903
|
console.error(" ! No LLM configured. Run `elisym init` to set up LLM.\n");
|
|
1945
1904
|
process.exit(1);
|
|
1946
1905
|
}
|
|
1947
|
-
|
|
1906
|
+
if (!loaded.secrets.llm_api_key) {
|
|
1907
|
+
console.error(
|
|
1908
|
+
` ! No LLM API key. Set ${loaded.yaml.llm.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"} env var or update the agent's secrets.
|
|
1909
|
+
`
|
|
1910
|
+
);
|
|
1911
|
+
process.exit(1);
|
|
1912
|
+
}
|
|
1913
|
+
const paths = agentPaths(loaded.dir);
|
|
1914
|
+
const skillsDir = paths.skills;
|
|
1948
1915
|
const allSkills = loadSkillsFromDir(skillsDir);
|
|
1949
1916
|
if (allSkills.length === 0) {
|
|
1950
1917
|
console.error(` ! No skills found in ${skillsDir}
|
|
1951
1918
|
`);
|
|
1952
1919
|
console.error(" Create a skill directory with a SKILL.md to get started.");
|
|
1953
|
-
console.error(
|
|
1920
|
+
console.error(` Example: ${skillsDir}/my-skill/SKILL.md
|
|
1921
|
+
`);
|
|
1954
1922
|
process.exit(1);
|
|
1955
1923
|
}
|
|
1956
1924
|
const registry = new SkillRegistry();
|
|
@@ -1966,50 +1934,69 @@ async function cmdStart(name) {
|
|
|
1966
1934
|
process.exit(1);
|
|
1967
1935
|
}
|
|
1968
1936
|
const llm = createLlmClient({
|
|
1969
|
-
provider:
|
|
1970
|
-
apiKey:
|
|
1971
|
-
model:
|
|
1972
|
-
maxTokens:
|
|
1937
|
+
provider: loaded.yaml.llm.provider,
|
|
1938
|
+
apiKey: loaded.secrets.llm_api_key,
|
|
1939
|
+
model: loaded.yaml.llm.model,
|
|
1940
|
+
maxTokens: loaded.yaml.llm.max_tokens,
|
|
1973
1941
|
logUsage: true
|
|
1974
1942
|
});
|
|
1975
1943
|
const skillCtx = {
|
|
1976
1944
|
llm,
|
|
1977
|
-
agentName
|
|
1978
|
-
agentDescription:
|
|
1945
|
+
agentName,
|
|
1946
|
+
agentDescription: loaded.yaml.description ?? ""
|
|
1979
1947
|
};
|
|
1980
1948
|
console.log(" Connecting to relays and publishing capabilities...");
|
|
1981
|
-
const identity = ElisymIdentity.fromHex(
|
|
1982
|
-
const relays =
|
|
1949
|
+
const identity = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
1950
|
+
const relays = loaded.yaml.relays.length > 0 ? loaded.yaml.relays : [...RELAYS];
|
|
1983
1951
|
const client = new ElisymClient({ relays });
|
|
1984
1952
|
const media = new MediaService();
|
|
1953
|
+
const mediaCache = await readMediaCache(loaded.dir);
|
|
1954
|
+
let mediaCacheDirty = false;
|
|
1955
|
+
const pictureUrl = await resolveMediaField(
|
|
1956
|
+
loaded.yaml.picture,
|
|
1957
|
+
loaded.dir,
|
|
1958
|
+
mediaCache,
|
|
1959
|
+
media,
|
|
1960
|
+
identity,
|
|
1961
|
+
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
1962
|
+
);
|
|
1963
|
+
const bannerUrl = await resolveMediaField(
|
|
1964
|
+
loaded.yaml.banner,
|
|
1965
|
+
loaded.dir,
|
|
1966
|
+
mediaCache,
|
|
1967
|
+
media,
|
|
1968
|
+
identity,
|
|
1969
|
+
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
1970
|
+
);
|
|
1985
1971
|
for (const skill of allSkills) {
|
|
1986
1972
|
if (skill.image || !skill.imageFile) {
|
|
1987
1973
|
continue;
|
|
1988
1974
|
}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1975
|
+
const skillRoot = join(skillsDir, skill.name);
|
|
1976
|
+
const cacheKey = `./skills/${skill.name}/${skill.imageFile}`;
|
|
1977
|
+
const absPath = join(skillRoot, skill.imageFile);
|
|
1978
|
+
const url = await uploadOrReuse(
|
|
1979
|
+
cacheKey,
|
|
1980
|
+
absPath,
|
|
1981
|
+
mediaCache,
|
|
1982
|
+
media,
|
|
1983
|
+
identity,
|
|
1984
|
+
() => mediaCacheDirty = true
|
|
1985
|
+
);
|
|
1986
|
+
if (url) {
|
|
1996
1987
|
skill.image = url;
|
|
1997
|
-
const skillMdPath = join(skillsDir, skill.name, "SKILL.md");
|
|
1998
|
-
const mdContent = readFileSync(skillMdPath, "utf-8");
|
|
1999
|
-
const updated = mdContent.replace(/^(image_file:\s*.+)$/m, (m) => `${m}
|
|
2000
|
-
image: ${url}`);
|
|
2001
|
-
writeFileSync(skillMdPath, updated);
|
|
2002
|
-
} catch (e) {
|
|
2003
|
-
console.warn(` ! Failed to upload image for "${skill.name}": ${e.message}`);
|
|
2004
1988
|
}
|
|
2005
1989
|
}
|
|
1990
|
+
if (mediaCacheDirty) {
|
|
1991
|
+
await writeMediaCache(loaded.dir, mediaCache);
|
|
1992
|
+
}
|
|
2006
1993
|
try {
|
|
2007
1994
|
await client.discovery.publishProfile(
|
|
2008
1995
|
identity,
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
1996
|
+
agentName,
|
|
1997
|
+
loaded.yaml.description ?? "",
|
|
1998
|
+
pictureUrl,
|
|
1999
|
+
bannerUrl
|
|
2013
2000
|
);
|
|
2014
2001
|
} catch (e) {
|
|
2015
2002
|
console.warn(` ! Failed to publish profile: ${e.message}`);
|
|
@@ -2075,7 +2062,7 @@ image: ${url}`);
|
|
|
2075
2062
|
}, HEARTBEAT_INTERVAL_MS);
|
|
2076
2063
|
}
|
|
2077
2064
|
const transport = new NostrTransport(client, identity, [DEFAULT_KIND_OFFSET]);
|
|
2078
|
-
const ledger = new JobLedger(
|
|
2065
|
+
const ledger = new JobLedger(paths.jobs);
|
|
2079
2066
|
const runtimeConfig = {
|
|
2080
2067
|
paymentTimeoutSecs: DEFAULTS.PAYMENT_EXPIRY_SECS,
|
|
2081
2068
|
maxConcurrentJobs: MAX_CONCURRENT_JOBS,
|
|
@@ -2114,15 +2101,88 @@ image: ${url}`);
|
|
|
2114
2101
|
console.log(" * Running. Press Ctrl+C to stop.\n");
|
|
2115
2102
|
await runtime.run();
|
|
2116
2103
|
}
|
|
2104
|
+
async function resolveMediaField(value, agentDir, cache, media, identity, onCacheUpdate) {
|
|
2105
|
+
if (!value) {
|
|
2106
|
+
return void 0;
|
|
2107
|
+
}
|
|
2108
|
+
if (isRemoteUrl(value)) {
|
|
2109
|
+
return value;
|
|
2110
|
+
}
|
|
2111
|
+
const absPath = resolveInsideAgentDir(value, agentDir);
|
|
2112
|
+
if (!absPath) {
|
|
2113
|
+
console.warn(` ! Skipping media field "${value}": path must stay inside the agent directory.`);
|
|
2114
|
+
return void 0;
|
|
2115
|
+
}
|
|
2116
|
+
return uploadOrReuse(value, absPath, cache, media, identity, () => onCacheUpdate(true));
|
|
2117
|
+
}
|
|
2118
|
+
function resolveInsideAgentDir(value, agentDir) {
|
|
2119
|
+
const agentRoot = resolve(agentDir);
|
|
2120
|
+
const candidate = resolve(agentRoot, value);
|
|
2121
|
+
const rel = relative(agentRoot, candidate);
|
|
2122
|
+
if (rel === "" || rel.startsWith("..") || rel.includes(`..${sep}`)) {
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
return candidate;
|
|
2126
|
+
}
|
|
2127
|
+
async function uploadOrReuse(cacheKey, absPath, cache, media, identity, onCacheUpdate) {
|
|
2128
|
+
try {
|
|
2129
|
+
const cached = await lookupCachedUrl(cache, cacheKey, absPath);
|
|
2130
|
+
if (cached) {
|
|
2131
|
+
return cached;
|
|
2132
|
+
}
|
|
2133
|
+
console.log(` Uploading ${basename(absPath)}...`);
|
|
2134
|
+
const data = readFileSync(absPath);
|
|
2135
|
+
const sha256 = createHash("sha256").update(data).digest("hex");
|
|
2136
|
+
const blob = new Blob([data]);
|
|
2137
|
+
const url = await media.upload(identity, blob, basename(absPath));
|
|
2138
|
+
cache[cacheKey] = newCacheEntry(url, sha256);
|
|
2139
|
+
onCacheUpdate();
|
|
2140
|
+
console.log(` Uploaded: ${url}`);
|
|
2141
|
+
return url;
|
|
2142
|
+
} catch (e) {
|
|
2143
|
+
console.warn(` ! Failed to upload ${basename(absPath)}: ${e.message}`);
|
|
2144
|
+
return void 0;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
function isRemoteUrl(value) {
|
|
2148
|
+
return /^https?:\/\//i.test(value);
|
|
2149
|
+
}
|
|
2117
2150
|
var MAX_PASSPHRASE_ATTEMPTS = 3;
|
|
2118
|
-
async function
|
|
2151
|
+
async function resolveStartAgentName(nameArg, cwd) {
|
|
2152
|
+
if (nameArg) {
|
|
2153
|
+
return nameArg;
|
|
2154
|
+
}
|
|
2155
|
+
const agents = await listAgents(cwd);
|
|
2156
|
+
if (agents.length === 0) {
|
|
2157
|
+
console.log("No agents configured. Run `elisym init` first.");
|
|
2158
|
+
process.exit(1);
|
|
2159
|
+
}
|
|
2160
|
+
const { default: inquirer } = await import('inquirer');
|
|
2161
|
+
const choices = [
|
|
2162
|
+
...agents.map((agent) => ({
|
|
2163
|
+
name: `${agent.name} (${agent.source}${agent.shadowsGlobal ? " - shadows global" : ""})`,
|
|
2164
|
+
value: agent.name
|
|
2165
|
+
})),
|
|
2166
|
+
{ name: "+ Create new agent", value: "__new__" }
|
|
2167
|
+
];
|
|
2168
|
+
const { selected } = await inquirer.prompt([
|
|
2169
|
+
{ type: "list", name: "selected", message: "Select agent to start", choices }
|
|
2170
|
+
]);
|
|
2171
|
+
if (selected === "__new__") {
|
|
2172
|
+
const { cmdInit: cmdInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2173
|
+
await cmdInit2();
|
|
2174
|
+
return void 0;
|
|
2175
|
+
}
|
|
2176
|
+
return selected;
|
|
2177
|
+
}
|
|
2178
|
+
async function loadAgentWithPrompt(name, cwd) {
|
|
2119
2179
|
const envPassphrase = process.env.ELISYM_PASSPHRASE;
|
|
2120
2180
|
try {
|
|
2121
|
-
return
|
|
2181
|
+
return await loadAgent(name, cwd, envPassphrase);
|
|
2122
2182
|
} catch (e) {
|
|
2123
|
-
const
|
|
2183
|
+
const isEncrypted3 = /encrypted secrets/i.test(e?.message ?? "");
|
|
2124
2184
|
const isWrongPassphrase = /Decryption failed/i.test(e?.message ?? "");
|
|
2125
|
-
if (!
|
|
2185
|
+
if (!isEncrypted3 && !isWrongPassphrase) {
|
|
2126
2186
|
throw e;
|
|
2127
2187
|
}
|
|
2128
2188
|
if (!process.stdin.isTTY) {
|
|
@@ -2140,7 +2200,7 @@ async function loadConfigWithPrompt(name) {
|
|
|
2140
2200
|
}
|
|
2141
2201
|
]);
|
|
2142
2202
|
try {
|
|
2143
|
-
return
|
|
2203
|
+
return await loadAgent(name, cwd, passphrase);
|
|
2144
2204
|
} catch (e) {
|
|
2145
2205
|
if (!/Decryption failed/i.test(e?.message ?? "")) {
|
|
2146
2206
|
throw e;
|
|
@@ -2155,12 +2215,10 @@ async function loadConfigWithPrompt(name) {
|
|
|
2155
2215
|
}
|
|
2156
2216
|
throw new Error("Unreachable");
|
|
2157
2217
|
}
|
|
2158
|
-
|
|
2159
|
-
// src/commands/wallet.ts
|
|
2160
|
-
init_config();
|
|
2161
2218
|
async function cmdWallet(name) {
|
|
2219
|
+
const cwd = process.cwd();
|
|
2162
2220
|
if (!name) {
|
|
2163
|
-
const agents = listAgents();
|
|
2221
|
+
const agents = await listAgents(cwd);
|
|
2164
2222
|
if (agents.length === 0) {
|
|
2165
2223
|
console.error("No agents found.");
|
|
2166
2224
|
process.exit(1);
|
|
@@ -2171,33 +2229,32 @@ async function cmdWallet(name) {
|
|
|
2171
2229
|
type: "list",
|
|
2172
2230
|
name: "selected",
|
|
2173
2231
|
message: "Select agent:",
|
|
2174
|
-
choices: agents
|
|
2232
|
+
choices: agents.map((agent) => ({
|
|
2233
|
+
name: `${agent.name} (${agent.source})`,
|
|
2234
|
+
value: agent.name
|
|
2235
|
+
}))
|
|
2175
2236
|
}
|
|
2176
2237
|
]);
|
|
2177
2238
|
name = selected;
|
|
2178
2239
|
}
|
|
2179
2240
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
2180
|
-
const
|
|
2181
|
-
const solPayment =
|
|
2241
|
+
const loaded = await loadAgent(name, cwd, passphrase);
|
|
2242
|
+
const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
2182
2243
|
if (!solPayment?.address) {
|
|
2183
2244
|
console.error("Solana address not configured for this agent.");
|
|
2184
2245
|
process.exit(1);
|
|
2185
2246
|
}
|
|
2186
|
-
const
|
|
2187
|
-
const rpcUrl = getRpcUrl(network);
|
|
2247
|
+
const rpcUrl = getRpcUrl(solPayment.network);
|
|
2188
2248
|
const rpc = createSolanaRpc(rpcUrl);
|
|
2189
2249
|
const walletAddress = address(solPayment.address);
|
|
2190
2250
|
const { value: balance } = await rpc.getBalance(walletAddress).send();
|
|
2191
2251
|
console.log(`
|
|
2192
2252
|
Agent: ${name}`);
|
|
2193
|
-
console.log(` Network: ${network}`);
|
|
2253
|
+
console.log(` Network: ${solPayment.network}`);
|
|
2194
2254
|
console.log(` Address: ${solPayment.address}`);
|
|
2195
2255
|
console.log(` Balance: ${formatSol(Number(balance))} (${balance} lamports)
|
|
2196
2256
|
`);
|
|
2197
2257
|
}
|
|
2198
|
-
|
|
2199
|
-
// src/index.ts
|
|
2200
|
-
init_config();
|
|
2201
2258
|
function readPackageVersion() {
|
|
2202
2259
|
try {
|
|
2203
2260
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2211,50 +2268,50 @@ var PACKAGE_VERSION = readPackageVersion();
|
|
|
2211
2268
|
|
|
2212
2269
|
// src/index.ts
|
|
2213
2270
|
process.removeAllListeners("warning");
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
program.command("profile [name]").description("Edit agent profile, wallet, and LLM settings").action(cmdProfile);
|
|
2217
|
-
program.command("start [name]").description("Start agent in provider mode").action(cmdStart);
|
|
2218
|
-
program.command("list").description("List all agents").action(() => {
|
|
2219
|
-
const agents = listAgents();
|
|
2220
|
-
if (agents.length === 0) {
|
|
2221
|
-
console.log("No agents found. Run `elisym init` to create one.");
|
|
2222
|
-
return;
|
|
2223
|
-
}
|
|
2224
|
-
console.log("\nAgents:");
|
|
2225
|
-
for (const name of agents) {
|
|
2271
|
+
function safe(fn) {
|
|
2272
|
+
return async (...args) => {
|
|
2226
2273
|
try {
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
const secretBytes = Buffer.from(secretKey, "hex");
|
|
2232
|
-
npub = secretBytes.length === 32 ? nip19.npubEncode(getPublicKey(secretBytes)) : "(invalid key)";
|
|
2233
|
-
}
|
|
2234
|
-
const solAddr = config.payments?.[0]?.address ? ` | Solana: ${config.payments[0].address}` : "";
|
|
2235
|
-
console.log(` ${name} | ${npub}${solAddr}`);
|
|
2236
|
-
} catch {
|
|
2237
|
-
console.log(` ${name} (error loading config)`);
|
|
2274
|
+
await fn(...args);
|
|
2275
|
+
} catch (e) {
|
|
2276
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
2277
|
+
process.exit(1);
|
|
2238
2278
|
}
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
program.command("
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
var program = new Command().name("elisym").description("CLI agent runner for the elisym network").version(PACKAGE_VERSION);
|
|
2282
|
+
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(
|
|
2283
|
+
safe(async (name, options) => {
|
|
2284
|
+
await cmdInit(name, options);
|
|
2285
|
+
})
|
|
2286
|
+
);
|
|
2287
|
+
program.command("profile [name]").description("Edit agent profile, wallet, and LLM settings").action(safe(cmdProfile));
|
|
2288
|
+
program.command("start [name]").description("Start agent in provider mode").action(safe(cmdStart));
|
|
2289
|
+
program.command("list").description("List all agents (project-local and home-global)").action(
|
|
2290
|
+
safe(async () => {
|
|
2291
|
+
const cwd = process.cwd();
|
|
2292
|
+
const agents = await listAgents(cwd);
|
|
2293
|
+
if (agents.length === 0) {
|
|
2294
|
+
console.log("No agents found. Run `elisym init` to create one.");
|
|
2295
|
+
return;
|
|
2251
2296
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2297
|
+
console.log("\nAgents:");
|
|
2298
|
+
for (const agent of agents) {
|
|
2299
|
+
try {
|
|
2300
|
+
const loaded = await loadAgent(agent.name, cwd);
|
|
2301
|
+
const identity = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
2302
|
+
const npub = nip19.npubEncode(identity.publicKey);
|
|
2303
|
+
const solAddr = loaded.yaml.payments[0]?.address ? ` | Solana: ${loaded.yaml.payments[0].address}` : "";
|
|
2304
|
+
const shadow = agent.shadowsGlobal ? " [shadows global]" : "";
|
|
2305
|
+
console.log(` ${agent.name} (${agent.source})${shadow} | ${npub}${solAddr}`);
|
|
2306
|
+
} catch (e) {
|
|
2307
|
+
const hint = /encrypted secrets/i.test(e?.message ?? "") ? " (encrypted)" : "";
|
|
2308
|
+
console.log(` ${agent.name} (${agent.source})${hint}`);
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
console.log();
|
|
2312
|
+
})
|
|
2313
|
+
);
|
|
2314
|
+
program.command("wallet [name]").description("Show wallet balance").action(safe(cmdWallet));
|
|
2258
2315
|
program.parse();
|
|
2259
2316
|
//# sourceMappingURL=index.js.map
|
|
2260
2317
|
//# sourceMappingURL=index.js.map
|