@elisym/cli 0.3.2 → 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 +482 -407
- 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([
|
|
183
232
|
{
|
|
184
233
|
type: "input",
|
|
185
|
-
name: "
|
|
186
|
-
message: "
|
|
234
|
+
name: "displayName",
|
|
235
|
+
message: "Display name (optional, for UI):",
|
|
187
236
|
default: ""
|
|
188
237
|
}
|
|
189
238
|
]);
|
|
190
|
-
const {
|
|
239
|
+
const { picture } = await inquirer.prompt([
|
|
191
240
|
{
|
|
192
241
|
type: "input",
|
|
193
|
-
name: "
|
|
194
|
-
message: "
|
|
242
|
+
name: "picture",
|
|
243
|
+
message: "Avatar file (relative to agent dir, e.g. ./avatar.png) or URL:",
|
|
244
|
+
default: ""
|
|
245
|
+
}
|
|
246
|
+
]);
|
|
247
|
+
const { banner } = await inquirer.prompt([
|
|
248
|
+
{
|
|
249
|
+
type: "input",
|
|
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,23 +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: [{ name: "devnet", value: "devnet" }],
|
|
218
|
-
default: "devnet"
|
|
219
|
-
}
|
|
220
|
-
]);
|
|
221
269
|
const { llmProvider } = await inquirer.prompt([
|
|
222
270
|
{
|
|
223
271
|
type: "list",
|
|
@@ -229,16 +277,21 @@ async function cmdInit() {
|
|
|
229
277
|
]
|
|
230
278
|
}
|
|
231
279
|
]);
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
}
|
|
240
293
|
console.log(" Fetching available models...");
|
|
241
|
-
const models = await fetchModels(llmProvider, apiKey);
|
|
294
|
+
const models = await fetchModels(llmProvider, apiKey ?? "");
|
|
242
295
|
const { model } = await inquirer.prompt([
|
|
243
296
|
{
|
|
244
297
|
type: "list",
|
|
@@ -255,77 +308,21 @@ async function cmdInit() {
|
|
|
255
308
|
default: 4096
|
|
256
309
|
}
|
|
257
310
|
]);
|
|
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
|
-
},
|
|
311
|
+
const yaml = ElisymYamlSchema.parse({
|
|
312
|
+
display_name: displayName || void 0,
|
|
313
|
+
description,
|
|
314
|
+
picture: picture || void 0,
|
|
315
|
+
banner: banner || void 0,
|
|
302
316
|
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
|
-
`);
|
|
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 };
|
|
324
322
|
}
|
|
325
323
|
var FALLBACK_MODELS;
|
|
326
324
|
var init_init = __esm({
|
|
327
325
|
"src/commands/init.ts"() {
|
|
328
|
-
init_config();
|
|
329
326
|
FALLBACK_MODELS = {
|
|
330
327
|
anthropic: ["claude-sonnet-4-6", "claude-haiku-4-5-20251001", "claude-opus-4-6"],
|
|
331
328
|
openai: ["gpt-4o", "gpt-4o-mini", "o3-mini"]
|
|
@@ -335,41 +332,32 @@ var init_init = __esm({
|
|
|
335
332
|
|
|
336
333
|
// src/index.ts
|
|
337
334
|
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
335
|
async function cmdProfile(name) {
|
|
356
336
|
const { default: inquirer } = await import('inquirer');
|
|
337
|
+
const cwd = process.cwd();
|
|
357
338
|
if (!name) {
|
|
358
|
-
const agents = listAgents();
|
|
339
|
+
const agents = await listAgents(cwd);
|
|
359
340
|
if (agents.length === 0) {
|
|
360
341
|
console.error("No agents found. Run `elisym init` first.");
|
|
361
342
|
process.exit(1);
|
|
362
343
|
}
|
|
363
344
|
const { selected } = await inquirer.prompt([
|
|
364
|
-
{
|
|
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
|
+
}
|
|
365
354
|
]);
|
|
366
355
|
name = selected;
|
|
367
356
|
}
|
|
368
357
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
369
|
-
const
|
|
370
|
-
const identity = ElisymIdentity.fromHex(config.identity.secret_key);
|
|
358
|
+
const loaded = await loadAgent(name, cwd, passphrase);
|
|
371
359
|
console.log(`
|
|
372
|
-
Editing agent "${name}"
|
|
360
|
+
Editing agent "${name}" (${loaded.source})
|
|
373
361
|
`);
|
|
374
362
|
let done = false;
|
|
375
363
|
while (!done) {
|
|
@@ -379,13 +367,16 @@ async function cmdProfile(name) {
|
|
|
379
367
|
name: "section",
|
|
380
368
|
message: "What to edit?",
|
|
381
369
|
choices: [
|
|
382
|
-
{ name: `Profile (name: ${config.identity.name})`, value: "profile" },
|
|
383
370
|
{
|
|
384
|
-
name: `
|
|
371
|
+
name: `Profile (description: ${truncate(loaded.yaml.description ?? "")})`,
|
|
372
|
+
value: "profile"
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: `Wallet (${loaded.yaml.payments[0]?.address ?? "not configured"})`,
|
|
385
376
|
value: "wallet"
|
|
386
377
|
},
|
|
387
378
|
{
|
|
388
|
-
name: `LLM (${
|
|
379
|
+
name: `LLM (${loaded.yaml.llm?.provider ?? "not configured"} / ${loaded.yaml.llm?.model ?? "-"})`,
|
|
389
380
|
value: "llm"
|
|
390
381
|
},
|
|
391
382
|
{ name: "Done", value: "done" }
|
|
@@ -398,70 +389,64 @@ async function cmdProfile(name) {
|
|
|
398
389
|
}
|
|
399
390
|
if (section === "profile") {
|
|
400
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
|
+
},
|
|
401
398
|
{
|
|
402
399
|
type: "input",
|
|
403
400
|
name: "description",
|
|
404
401
|
message: "Description:",
|
|
405
|
-
default:
|
|
402
|
+
default: loaded.yaml.description ?? ""
|
|
406
403
|
},
|
|
407
404
|
{
|
|
408
405
|
type: "input",
|
|
409
406
|
name: "picture",
|
|
410
|
-
message: "Avatar (
|
|
411
|
-
default:
|
|
407
|
+
message: "Avatar (relative path or URL, empty to clear):",
|
|
408
|
+
default: loaded.yaml.picture ?? ""
|
|
412
409
|
},
|
|
413
410
|
{
|
|
414
411
|
type: "input",
|
|
415
412
|
name: "banner",
|
|
416
|
-
message: "Banner (
|
|
417
|
-
default:
|
|
413
|
+
message: "Banner (relative path or URL, empty to clear):",
|
|
414
|
+
default: loaded.yaml.banner ?? ""
|
|
418
415
|
}
|
|
419
416
|
]);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
} else {
|
|
430
|
-
config.identity.banner = void 0;
|
|
431
|
-
}
|
|
432
|
-
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;
|
|
433
426
|
console.log(" Profile updated.\n");
|
|
434
427
|
}
|
|
435
428
|
if (section === "wallet") {
|
|
436
|
-
const
|
|
437
|
-
const currentNetwork = config.payments?.[0]?.network ?? "devnet";
|
|
429
|
+
const current = loaded.yaml.payments[0];
|
|
438
430
|
const answers = await inquirer.prompt([
|
|
439
431
|
{
|
|
440
432
|
type: "input",
|
|
441
433
|
name: "address",
|
|
442
|
-
message: "Solana address:",
|
|
443
|
-
default:
|
|
444
|
-
validate: (
|
|
445
|
-
if (!
|
|
434
|
+
message: "Solana address (empty to clear):",
|
|
435
|
+
default: current?.address ?? "",
|
|
436
|
+
validate: (value) => {
|
|
437
|
+
if (!value) {
|
|
446
438
|
return true;
|
|
447
439
|
}
|
|
448
|
-
return isAddress(
|
|
440
|
+
return isAddress(value) || "Invalid Solana address";
|
|
449
441
|
}
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
type: "list",
|
|
453
|
-
name: "network",
|
|
454
|
-
message: "Network:",
|
|
455
|
-
choices: ["devnet"],
|
|
456
|
-
default: currentNetwork
|
|
457
442
|
}
|
|
458
443
|
]);
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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;
|
|
465
450
|
console.log(" Wallet updated.\n");
|
|
466
451
|
}
|
|
467
452
|
if (section === "llm") {
|
|
@@ -474,33 +459,29 @@ async function cmdProfile(name) {
|
|
|
474
459
|
{ name: "Anthropic (Claude)", value: "anthropic" },
|
|
475
460
|
{ name: "OpenAI (GPT)", value: "openai" }
|
|
476
461
|
],
|
|
477
|
-
default:
|
|
462
|
+
default: loaded.yaml.llm?.provider ?? "anthropic"
|
|
478
463
|
}
|
|
479
464
|
]);
|
|
480
465
|
const { apiKey } = await inquirer.prompt([
|
|
481
466
|
{
|
|
482
467
|
type: "password",
|
|
483
468
|
name: "apiKey",
|
|
484
|
-
message:
|
|
469
|
+
message: "API key (leave empty to keep current):",
|
|
485
470
|
mask: "*"
|
|
486
471
|
}
|
|
487
472
|
]);
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
if (isEncrypted(plainKey) && passphrase) {
|
|
491
|
-
const { decryptSecret } = await import('@elisym/sdk/node');
|
|
492
|
-
plainKey = decryptSecret(plainKey, passphrase);
|
|
493
|
-
}
|
|
473
|
+
const currentKeyPlain = loaded.secrets.llm_api_key && !isEncrypted(loaded.secrets.llm_api_key) ? loaded.secrets.llm_api_key : "";
|
|
474
|
+
const probeKey = apiKey || currentKeyPlain;
|
|
494
475
|
console.log(" Fetching available models...");
|
|
495
476
|
const { fetchModels: fetchModels2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
496
|
-
const models = await fetchModels2(llmProvider,
|
|
477
|
+
const models = await fetchModels2(llmProvider, probeKey);
|
|
497
478
|
const { model } = await inquirer.prompt([
|
|
498
479
|
{
|
|
499
480
|
type: "list",
|
|
500
481
|
name: "model",
|
|
501
482
|
message: "Model:",
|
|
502
483
|
choices: models,
|
|
503
|
-
default:
|
|
484
|
+
default: loaded.yaml.llm?.model
|
|
504
485
|
}
|
|
505
486
|
]);
|
|
506
487
|
const { maxTokens } = await inquirer.prompt([
|
|
@@ -508,26 +489,29 @@ async function cmdProfile(name) {
|
|
|
508
489
|
type: "number",
|
|
509
490
|
name: "maxTokens",
|
|
510
491
|
message: "Max tokens:",
|
|
511
|
-
default:
|
|
492
|
+
default: loaded.yaml.llm?.max_tokens ?? 4096
|
|
512
493
|
}
|
|
513
494
|
]);
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
+
}
|
|
522
503
|
console.log(" LLM updated.\n");
|
|
523
504
|
}
|
|
524
505
|
}
|
|
525
506
|
console.log(` Agent "${name}" saved.
|
|
526
507
|
`);
|
|
527
508
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
509
|
+
function truncate(value, max = 40) {
|
|
510
|
+
if (value.length <= max) {
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
return value.slice(0, max - 1) + "\u2026";
|
|
514
|
+
}
|
|
531
515
|
var HEARTBEAT_INTERVAL_MS = 10 * 60 * 1e3;
|
|
532
516
|
var MAX_CONCURRENT_JOBS = 10;
|
|
533
517
|
var RECOVERY_MAX_RETRIES = 5;
|
|
@@ -552,8 +536,13 @@ var VALID_TRANSITIONS = {
|
|
|
552
536
|
var JobLedger = class {
|
|
553
537
|
entries = /* @__PURE__ */ new Map();
|
|
554
538
|
path;
|
|
555
|
-
|
|
556
|
-
|
|
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;
|
|
557
546
|
this.load();
|
|
558
547
|
}
|
|
559
548
|
load() {
|
|
@@ -677,7 +666,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
677
666
|
if (!signal) {
|
|
678
667
|
return new Promise((r) => setTimeout(r, ms));
|
|
679
668
|
}
|
|
680
|
-
return new Promise((
|
|
669
|
+
return new Promise((resolve3, reject) => {
|
|
681
670
|
const cleanup = () => {
|
|
682
671
|
clearTimeout(timer);
|
|
683
672
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -688,7 +677,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
688
677
|
};
|
|
689
678
|
const timer = setTimeout(() => {
|
|
690
679
|
cleanup();
|
|
691
|
-
|
|
680
|
+
resolve3();
|
|
692
681
|
}, ms);
|
|
693
682
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
694
683
|
});
|
|
@@ -1055,8 +1044,8 @@ var AgentRuntime = class {
|
|
|
1055
1044
|
});
|
|
1056
1045
|
});
|
|
1057
1046
|
log("Agent runtime started. Listening for jobs...");
|
|
1058
|
-
await new Promise((
|
|
1059
|
-
this.abortController.signal.addEventListener("abort", () =>
|
|
1047
|
+
await new Promise((resolve3) => {
|
|
1048
|
+
this.abortController.signal.addEventListener("abort", () => resolve3(), { once: true });
|
|
1060
1049
|
process.on("SIGINT", () => {
|
|
1061
1050
|
log("Shutting down...");
|
|
1062
1051
|
this.stop();
|
|
@@ -1553,7 +1542,7 @@ var ScriptSkill = class {
|
|
|
1553
1542
|
throw new Error(`Max tool rounds (${this.maxToolRounds}) exceeded`);
|
|
1554
1543
|
}
|
|
1555
1544
|
runTool(toolDef, call, signal) {
|
|
1556
|
-
return new Promise((
|
|
1545
|
+
return new Promise((resolve3, _reject) => {
|
|
1557
1546
|
const args = [...toolDef.command];
|
|
1558
1547
|
const cmd = args.shift();
|
|
1559
1548
|
const params = toolDef.parameters ?? [];
|
|
@@ -1596,13 +1585,13 @@ var ScriptSkill = class {
|
|
|
1596
1585
|
});
|
|
1597
1586
|
child.on("close", (code) => {
|
|
1598
1587
|
if (code === 0) {
|
|
1599
|
-
|
|
1588
|
+
resolve3(stdout.trim());
|
|
1600
1589
|
} else {
|
|
1601
|
-
|
|
1590
|
+
resolve3(`Error (exit ${code}): ${stderr.trim() || stdout.trim()}`);
|
|
1602
1591
|
}
|
|
1603
1592
|
});
|
|
1604
1593
|
child.on("error", (err) => {
|
|
1605
|
-
|
|
1594
|
+
resolve3(`Error: ${err.message}`);
|
|
1606
1595
|
});
|
|
1607
1596
|
});
|
|
1608
1597
|
}
|
|
@@ -1868,37 +1857,25 @@ function startWatchdog(deps) {
|
|
|
1868
1857
|
}
|
|
1869
1858
|
|
|
1870
1859
|
// 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;
|
|
1860
|
+
async function cmdStart(nameArg) {
|
|
1861
|
+
const cwd = process.cwd();
|
|
1862
|
+
const agentName = await resolveStartAgentName(nameArg, cwd);
|
|
1863
|
+
if (!agentName) {
|
|
1864
|
+
return;
|
|
1889
1865
|
}
|
|
1890
|
-
const
|
|
1866
|
+
const loaded = await loadAgentWithPrompt(agentName, cwd);
|
|
1891
1867
|
console.log(`
|
|
1892
|
-
Starting agent ${
|
|
1868
|
+
Starting agent ${agentName} (${loaded.source})...
|
|
1893
1869
|
`);
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
}
|
|
1870
|
+
if (loaded.shadowsGlobal) {
|
|
1871
|
+
console.log(
|
|
1872
|
+
` ! Using project-local ${agentName} (shadows global agent in ~/.elisym/${agentName}/)
|
|
1873
|
+
`
|
|
1874
|
+
);
|
|
1900
1875
|
}
|
|
1901
|
-
const
|
|
1876
|
+
const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
1877
|
+
const solanaAddress = solPayment?.address;
|
|
1878
|
+
const walletNetwork = solPayment?.network ?? "devnet";
|
|
1902
1879
|
if (solanaAddress) {
|
|
1903
1880
|
try {
|
|
1904
1881
|
const rpcUrl = getRpcUrl(walletNetwork);
|
|
@@ -1922,17 +1899,26 @@ async function cmdStart(name) {
|
|
|
1922
1899
|
`);
|
|
1923
1900
|
}
|
|
1924
1901
|
}
|
|
1925
|
-
if (!
|
|
1902
|
+
if (!loaded.yaml.llm) {
|
|
1926
1903
|
console.error(" ! No LLM configured. Run `elisym init` to set up LLM.\n");
|
|
1927
1904
|
process.exit(1);
|
|
1928
1905
|
}
|
|
1929
|
-
|
|
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;
|
|
1930
1915
|
const allSkills = loadSkillsFromDir(skillsDir);
|
|
1931
1916
|
if (allSkills.length === 0) {
|
|
1932
1917
|
console.error(` ! No skills found in ${skillsDir}
|
|
1933
1918
|
`);
|
|
1934
1919
|
console.error(" Create a skill directory with a SKILL.md to get started.");
|
|
1935
|
-
console.error(
|
|
1920
|
+
console.error(` Example: ${skillsDir}/my-skill/SKILL.md
|
|
1921
|
+
`);
|
|
1936
1922
|
process.exit(1);
|
|
1937
1923
|
}
|
|
1938
1924
|
const registry = new SkillRegistry();
|
|
@@ -1948,50 +1934,69 @@ async function cmdStart(name) {
|
|
|
1948
1934
|
process.exit(1);
|
|
1949
1935
|
}
|
|
1950
1936
|
const llm = createLlmClient({
|
|
1951
|
-
provider:
|
|
1952
|
-
apiKey:
|
|
1953
|
-
model:
|
|
1954
|
-
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,
|
|
1955
1941
|
logUsage: true
|
|
1956
1942
|
});
|
|
1957
1943
|
const skillCtx = {
|
|
1958
1944
|
llm,
|
|
1959
|
-
agentName
|
|
1960
|
-
agentDescription:
|
|
1945
|
+
agentName,
|
|
1946
|
+
agentDescription: loaded.yaml.description ?? ""
|
|
1961
1947
|
};
|
|
1962
1948
|
console.log(" Connecting to relays and publishing capabilities...");
|
|
1963
|
-
const identity = ElisymIdentity.fromHex(
|
|
1964
|
-
const relays =
|
|
1949
|
+
const identity = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
1950
|
+
const relays = loaded.yaml.relays.length > 0 ? loaded.yaml.relays : [...RELAYS];
|
|
1965
1951
|
const client = new ElisymClient({ relays });
|
|
1966
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
|
+
);
|
|
1967
1971
|
for (const skill of allSkills) {
|
|
1968
1972
|
if (skill.image || !skill.imageFile) {
|
|
1969
1973
|
continue;
|
|
1970
1974
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
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) {
|
|
1978
1987
|
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
1988
|
}
|
|
1987
1989
|
}
|
|
1990
|
+
if (mediaCacheDirty) {
|
|
1991
|
+
await writeMediaCache(loaded.dir, mediaCache);
|
|
1992
|
+
}
|
|
1988
1993
|
try {
|
|
1989
1994
|
await client.discovery.publishProfile(
|
|
1990
1995
|
identity,
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1996
|
+
agentName,
|
|
1997
|
+
loaded.yaml.description ?? "",
|
|
1998
|
+
pictureUrl,
|
|
1999
|
+
bannerUrl
|
|
1995
2000
|
);
|
|
1996
2001
|
} catch (e) {
|
|
1997
2002
|
console.warn(` ! Failed to publish profile: ${e.message}`);
|
|
@@ -2057,7 +2062,7 @@ image: ${url}`);
|
|
|
2057
2062
|
}, HEARTBEAT_INTERVAL_MS);
|
|
2058
2063
|
}
|
|
2059
2064
|
const transport = new NostrTransport(client, identity, [DEFAULT_KIND_OFFSET]);
|
|
2060
|
-
const ledger = new JobLedger(
|
|
2065
|
+
const ledger = new JobLedger(paths.jobs);
|
|
2061
2066
|
const runtimeConfig = {
|
|
2062
2067
|
paymentTimeoutSecs: DEFAULTS.PAYMENT_EXPIRY_SECS,
|
|
2063
2068
|
maxConcurrentJobs: MAX_CONCURRENT_JOBS,
|
|
@@ -2096,15 +2101,88 @@ image: ${url}`);
|
|
|
2096
2101
|
console.log(" * Running. Press Ctrl+C to stop.\n");
|
|
2097
2102
|
await runtime.run();
|
|
2098
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
|
+
}
|
|
2099
2150
|
var MAX_PASSPHRASE_ATTEMPTS = 3;
|
|
2100
|
-
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) {
|
|
2101
2179
|
const envPassphrase = process.env.ELISYM_PASSPHRASE;
|
|
2102
2180
|
try {
|
|
2103
|
-
return
|
|
2181
|
+
return await loadAgent(name, cwd, envPassphrase);
|
|
2104
2182
|
} catch (e) {
|
|
2105
|
-
const
|
|
2183
|
+
const isEncrypted3 = /encrypted secrets/i.test(e?.message ?? "");
|
|
2106
2184
|
const isWrongPassphrase = /Decryption failed/i.test(e?.message ?? "");
|
|
2107
|
-
if (!
|
|
2185
|
+
if (!isEncrypted3 && !isWrongPassphrase) {
|
|
2108
2186
|
throw e;
|
|
2109
2187
|
}
|
|
2110
2188
|
if (!process.stdin.isTTY) {
|
|
@@ -2122,7 +2200,7 @@ async function loadConfigWithPrompt(name) {
|
|
|
2122
2200
|
}
|
|
2123
2201
|
]);
|
|
2124
2202
|
try {
|
|
2125
|
-
return
|
|
2203
|
+
return await loadAgent(name, cwd, passphrase);
|
|
2126
2204
|
} catch (e) {
|
|
2127
2205
|
if (!/Decryption failed/i.test(e?.message ?? "")) {
|
|
2128
2206
|
throw e;
|
|
@@ -2137,12 +2215,10 @@ async function loadConfigWithPrompt(name) {
|
|
|
2137
2215
|
}
|
|
2138
2216
|
throw new Error("Unreachable");
|
|
2139
2217
|
}
|
|
2140
|
-
|
|
2141
|
-
// src/commands/wallet.ts
|
|
2142
|
-
init_config();
|
|
2143
2218
|
async function cmdWallet(name) {
|
|
2219
|
+
const cwd = process.cwd();
|
|
2144
2220
|
if (!name) {
|
|
2145
|
-
const agents = listAgents();
|
|
2221
|
+
const agents = await listAgents(cwd);
|
|
2146
2222
|
if (agents.length === 0) {
|
|
2147
2223
|
console.error("No agents found.");
|
|
2148
2224
|
process.exit(1);
|
|
@@ -2153,33 +2229,32 @@ async function cmdWallet(name) {
|
|
|
2153
2229
|
type: "list",
|
|
2154
2230
|
name: "selected",
|
|
2155
2231
|
message: "Select agent:",
|
|
2156
|
-
choices: agents
|
|
2232
|
+
choices: agents.map((agent) => ({
|
|
2233
|
+
name: `${agent.name} (${agent.source})`,
|
|
2234
|
+
value: agent.name
|
|
2235
|
+
}))
|
|
2157
2236
|
}
|
|
2158
2237
|
]);
|
|
2159
2238
|
name = selected;
|
|
2160
2239
|
}
|
|
2161
2240
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
2162
|
-
const
|
|
2163
|
-
const solPayment =
|
|
2241
|
+
const loaded = await loadAgent(name, cwd, passphrase);
|
|
2242
|
+
const solPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
2164
2243
|
if (!solPayment?.address) {
|
|
2165
2244
|
console.error("Solana address not configured for this agent.");
|
|
2166
2245
|
process.exit(1);
|
|
2167
2246
|
}
|
|
2168
|
-
const
|
|
2169
|
-
const rpcUrl = getRpcUrl();
|
|
2247
|
+
const rpcUrl = getRpcUrl(solPayment.network);
|
|
2170
2248
|
const rpc = createSolanaRpc(rpcUrl);
|
|
2171
2249
|
const walletAddress = address(solPayment.address);
|
|
2172
2250
|
const { value: balance } = await rpc.getBalance(walletAddress).send();
|
|
2173
2251
|
console.log(`
|
|
2174
2252
|
Agent: ${name}`);
|
|
2175
|
-
console.log(` Network: ${network}`);
|
|
2253
|
+
console.log(` Network: ${solPayment.network}`);
|
|
2176
2254
|
console.log(` Address: ${solPayment.address}`);
|
|
2177
2255
|
console.log(` Balance: ${formatSol(Number(balance))} (${balance} lamports)
|
|
2178
2256
|
`);
|
|
2179
2257
|
}
|
|
2180
|
-
|
|
2181
|
-
// src/index.ts
|
|
2182
|
-
init_config();
|
|
2183
2258
|
function readPackageVersion() {
|
|
2184
2259
|
try {
|
|
2185
2260
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2193,50 +2268,50 @@ var PACKAGE_VERSION = readPackageVersion();
|
|
|
2193
2268
|
|
|
2194
2269
|
// src/index.ts
|
|
2195
2270
|
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) {
|
|
2271
|
+
function safe(fn) {
|
|
2272
|
+
return async (...args) => {
|
|
2208
2273
|
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)`);
|
|
2274
|
+
await fn(...args);
|
|
2275
|
+
} catch (e) {
|
|
2276
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
2277
|
+
process.exit(1);
|
|
2220
2278
|
}
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
program.command("
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
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;
|
|
2233
2296
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
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));
|
|
2240
2315
|
program.parse();
|
|
2241
2316
|
//# sourceMappingURL=index.js.map
|
|
2242
2317
|
//# sourceMappingURL=index.js.map
|