@elisym/cli 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +120 -34
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/skills-examples/usdc-summarize/SKILL.md +17 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-deprecation
|
|
2
2
|
import { readFileSync, readdirSync, statSync, renameSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, resolve, basename, relative, sep } from 'node:path';
|
|
4
|
-
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, DEFAULTS, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet } from '@elisym/sdk';
|
|
4
|
+
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, DEFAULTS, USDC_SOLANA_DEVNET, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet, NATIVE_SOL } from '@elisym/sdk';
|
|
5
5
|
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYaml, writeSecrets, listAgents, loadAgent, agentPaths, readMediaCache, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
6
6
|
import { isAddress, createSolanaRpc, address } from '@solana/kit';
|
|
7
7
|
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
|
|
@@ -268,6 +268,11 @@ async function promptYaml(inquirer) {
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
]);
|
|
271
|
+
if (solanaAddress) {
|
|
272
|
+
console.log(
|
|
273
|
+
" The wallet receives every asset on Solana (SOL directly, USDC and other\n SPL tokens via their ATA). Each skill declares its own price and token\n in SKILL.md. Fund with SOL via `solana airdrop` or USDC via https://faucet.circle.com."
|
|
274
|
+
);
|
|
275
|
+
}
|
|
271
276
|
const { llmProvider } = await inquirer.prompt([
|
|
272
277
|
{
|
|
273
278
|
type: "list",
|
|
@@ -1114,7 +1119,11 @@ var LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
1114
1119
|
var TOTAL_JOB_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1115
1120
|
function resolveJobPrice(tags, skills) {
|
|
1116
1121
|
const skill = skills.route(tags);
|
|
1117
|
-
return skill?.
|
|
1122
|
+
return skill?.priceSubunits ?? 0;
|
|
1123
|
+
}
|
|
1124
|
+
function resolveJobAsset(tags, skills) {
|
|
1125
|
+
const skill = skills.route(tags);
|
|
1126
|
+
return skill?.asset ?? NATIVE_SOL;
|
|
1118
1127
|
}
|
|
1119
1128
|
var RATE_LIMIT_WINDOW_MS = 10 * 60 * 1e3;
|
|
1120
1129
|
var MAX_JOBS_PER_CUSTOMER = 20;
|
|
@@ -1224,16 +1233,18 @@ var AgentRuntime = class {
|
|
|
1224
1233
|
log("Agent runtime started. Listening for jobs...");
|
|
1225
1234
|
await new Promise((resolve3) => {
|
|
1226
1235
|
this.abortController.signal.addEventListener("abort", () => resolve3(), { once: true });
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1236
|
+
let shuttingDown = false;
|
|
1237
|
+
const onSignal = () => {
|
|
1238
|
+
if (shuttingDown) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
shuttingDown = true;
|
|
1233
1242
|
log("Shutting down...");
|
|
1234
1243
|
this.stop();
|
|
1235
1244
|
setTimeout(() => process.exit(0), 3e3).unref();
|
|
1236
|
-
}
|
|
1245
|
+
};
|
|
1246
|
+
process.on("SIGINT", onSignal);
|
|
1247
|
+
process.on("SIGTERM", onSignal);
|
|
1237
1248
|
});
|
|
1238
1249
|
}
|
|
1239
1250
|
/** Drop expired hits from both sliding-window limiters. */
|
|
@@ -1304,6 +1315,7 @@ var AgentRuntime = class {
|
|
|
1304
1315
|
throw new Error(`Input too long: ${job.input.length} chars (max ${LIMITS.MAX_INPUT_LENGTH})`);
|
|
1305
1316
|
}
|
|
1306
1317
|
const jobPrice = resolveJobPrice(job.tags, this.skills);
|
|
1318
|
+
const jobAsset = resolveJobAsset(job.tags, this.skills);
|
|
1307
1319
|
let netAmount;
|
|
1308
1320
|
let paymentRequest;
|
|
1309
1321
|
this.ledger.recordPaid({
|
|
@@ -1318,11 +1330,16 @@ var AgentRuntime = class {
|
|
|
1318
1330
|
created_at: Math.floor(Date.now() / 1e3)
|
|
1319
1331
|
});
|
|
1320
1332
|
if (jobPrice > 0) {
|
|
1321
|
-
const result = await this.collectPayment(job, jobPrice, signal);
|
|
1333
|
+
const result = await this.collectPayment(job, jobPrice, jobAsset, signal);
|
|
1322
1334
|
netAmount = result.netAmount;
|
|
1323
1335
|
paymentRequest = result.paymentRequest;
|
|
1324
1336
|
this.ledger.updatePayment(job.jobId, netAmount, paymentRequest);
|
|
1325
|
-
log(
|
|
1337
|
+
log(
|
|
1338
|
+
`[${job.jobId.slice(0, 8)}] Payment confirmed: ${formatAssetAmount(
|
|
1339
|
+
jobAsset,
|
|
1340
|
+
BigInt(netAmount)
|
|
1341
|
+
)}`
|
|
1342
|
+
);
|
|
1326
1343
|
this.callbacks.onPaymentReceived?.(job.jobId, netAmount);
|
|
1327
1344
|
}
|
|
1328
1345
|
await this.transport.sendFeedback(job, { type: "processing" }).catch(() => {
|
|
@@ -1352,7 +1369,7 @@ var AgentRuntime = class {
|
|
|
1352
1369
|
* Collect payment for a job. Creates payment request, sends PaymentRequired feedback,
|
|
1353
1370
|
* polls for on-chain confirmation. Aborts if signal fires.
|
|
1354
1371
|
*/
|
|
1355
|
-
async collectPayment(job, jobPrice, signal) {
|
|
1372
|
+
async collectPayment(job, jobPrice, jobAsset, signal) {
|
|
1356
1373
|
const log = this.callbacks.onLog ?? console.log;
|
|
1357
1374
|
if (!this.config.solanaAddress) {
|
|
1358
1375
|
throw new Error("Solana address not configured");
|
|
@@ -1362,12 +1379,14 @@ var AgentRuntime = class {
|
|
|
1362
1379
|
this.config.solanaAddress,
|
|
1363
1380
|
jobPrice,
|
|
1364
1381
|
protocolConfig,
|
|
1365
|
-
{ expirySecs: this.config.paymentTimeoutSecs }
|
|
1382
|
+
{ expirySecs: this.config.paymentTimeoutSecs, asset: jobAsset }
|
|
1366
1383
|
);
|
|
1367
1384
|
const requestJson = JSON.stringify(request);
|
|
1368
1385
|
const fee = calculateProtocolFee(jobPrice, protocolConfig.feeBps);
|
|
1369
1386
|
const netAmount = jobPrice - fee;
|
|
1370
|
-
log(
|
|
1387
|
+
log(
|
|
1388
|
+
`[${job.jobId.slice(0, 8)}] Payment required: ${formatAssetAmount(jobAsset, BigInt(jobPrice))} (fee: ${formatAssetAmount(jobAsset, BigInt(fee))})`
|
|
1389
|
+
);
|
|
1371
1390
|
this.ledger.updatePayment(job.jobId, void 0, requestJson);
|
|
1372
1391
|
await this.transport.sendFeedback(job, {
|
|
1373
1392
|
type: "payment-required",
|
|
@@ -1492,11 +1511,11 @@ var AgentRuntime = class {
|
|
|
1492
1511
|
this.ledger.markFailed(entry.job_id);
|
|
1493
1512
|
return;
|
|
1494
1513
|
}
|
|
1495
|
-
if (skill.
|
|
1514
|
+
if (skill.priceSubunits > 0 && !entry.net_amount) {
|
|
1496
1515
|
if (entry.payment_request) {
|
|
1497
1516
|
const verified = await this.reVerifyPayment(
|
|
1498
1517
|
entry,
|
|
1499
|
-
skill.
|
|
1518
|
+
skill.priceSubunits,
|
|
1500
1519
|
log,
|
|
1501
1520
|
recoveryAbort.signal
|
|
1502
1521
|
);
|
|
@@ -1537,7 +1556,7 @@ var AgentRuntime = class {
|
|
|
1537
1556
|
* job will be marked failed. For mainnet: use monitoring, avoid extended downtime, or
|
|
1538
1557
|
* configure an archive RPC via SOLANA_RPC_URL.
|
|
1539
1558
|
*/
|
|
1540
|
-
async reVerifyPayment(entry,
|
|
1559
|
+
async reVerifyPayment(entry, priceSubunits, log, signal) {
|
|
1541
1560
|
try {
|
|
1542
1561
|
const request = JSON.parse(entry.payment_request);
|
|
1543
1562
|
const rpc = createSolanaRpc(getRpcUrl(this.config.network));
|
|
@@ -1571,10 +1590,10 @@ var AgentRuntime = class {
|
|
|
1571
1590
|
result = await payment.verifyPayment(rpc, request, protocolConfig);
|
|
1572
1591
|
}
|
|
1573
1592
|
if (result.verified) {
|
|
1574
|
-
const fee = calculateProtocolFee(
|
|
1575
|
-
const netAmount =
|
|
1593
|
+
const fee = calculateProtocolFee(priceSubunits, protocolConfig.feeBps);
|
|
1594
|
+
const netAmount = priceSubunits - fee;
|
|
1576
1595
|
this.ledger.updatePayment(entry.job_id, netAmount, entry.payment_request);
|
|
1577
|
-
log(`[${entry.job_id.slice(0, 8)}] Recovery: payment re-verified (${netAmount}
|
|
1596
|
+
log(`[${entry.job_id.slice(0, 8)}] Recovery: payment re-verified (${netAmount} subunits)`);
|
|
1578
1597
|
return true;
|
|
1579
1598
|
}
|
|
1580
1599
|
log(`[${entry.job_id.slice(0, 8)}] Recovery: payment not found on-chain, marking failed`);
|
|
@@ -1597,13 +1616,22 @@ var SkillRegistry = class {
|
|
|
1597
1616
|
this.skills.push(skill);
|
|
1598
1617
|
}
|
|
1599
1618
|
route(tags) {
|
|
1600
|
-
for (const
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1619
|
+
for (const tag of tags) {
|
|
1620
|
+
const byName = this.skills.find((skill) => skill.name === tag);
|
|
1621
|
+
if (byName) {
|
|
1622
|
+
return byName;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
for (const tag of tags) {
|
|
1626
|
+
const byCap = this.skills.find((skill) => skill.capabilities.includes(tag));
|
|
1627
|
+
if (byCap) {
|
|
1628
|
+
return byCap;
|
|
1605
1629
|
}
|
|
1606
1630
|
}
|
|
1631
|
+
const hasSpecificTag = tags.some((tag) => tag && tag !== "elisym");
|
|
1632
|
+
if (hasSpecificTag) {
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1607
1635
|
return this.defaultIndex !== null ? this.skills[this.defaultIndex] : null;
|
|
1608
1636
|
}
|
|
1609
1637
|
allCapabilities() {
|
|
@@ -1620,22 +1648,49 @@ var ScriptSkill = class {
|
|
|
1620
1648
|
name;
|
|
1621
1649
|
description;
|
|
1622
1650
|
capabilities;
|
|
1623
|
-
|
|
1651
|
+
priceSubunits;
|
|
1652
|
+
asset;
|
|
1624
1653
|
image;
|
|
1625
1654
|
imageFile;
|
|
1626
1655
|
inner;
|
|
1627
|
-
constructor(name, description, capabilities,
|
|
1656
|
+
constructor(name, description, capabilities, priceSubunits, assetOrImage, imageOrImageFile, imageFileOrDir, dirOrPrompt, promptOrTools, toolsOrRounds, rounds) {
|
|
1657
|
+
let asset;
|
|
1658
|
+
let image;
|
|
1659
|
+
let imageFile;
|
|
1660
|
+
let skillDir;
|
|
1661
|
+
let systemPrompt;
|
|
1662
|
+
let tools;
|
|
1663
|
+
let maxToolRounds;
|
|
1664
|
+
if (assetOrImage !== void 0 && typeof assetOrImage === "object" && "token" in assetOrImage) {
|
|
1665
|
+
asset = assetOrImage;
|
|
1666
|
+
image = imageOrImageFile;
|
|
1667
|
+
imageFile = imageFileOrDir;
|
|
1668
|
+
skillDir = dirOrPrompt;
|
|
1669
|
+
systemPrompt = promptOrTools;
|
|
1670
|
+
tools = toolsOrRounds;
|
|
1671
|
+
maxToolRounds = rounds;
|
|
1672
|
+
} else {
|
|
1673
|
+
asset = NATIVE_SOL;
|
|
1674
|
+
image = assetOrImage;
|
|
1675
|
+
imageFile = imageOrImageFile;
|
|
1676
|
+
skillDir = imageFileOrDir;
|
|
1677
|
+
systemPrompt = dirOrPrompt;
|
|
1678
|
+
tools = promptOrTools;
|
|
1679
|
+
maxToolRounds = toolsOrRounds;
|
|
1680
|
+
}
|
|
1628
1681
|
this.name = name;
|
|
1629
1682
|
this.description = description;
|
|
1630
1683
|
this.capabilities = capabilities;
|
|
1631
|
-
this.
|
|
1684
|
+
this.priceSubunits = priceSubunits;
|
|
1685
|
+
this.asset = asset;
|
|
1632
1686
|
this.image = image;
|
|
1633
1687
|
this.imageFile = imageFile;
|
|
1634
1688
|
this.inner = new ScriptSkill$1({
|
|
1635
1689
|
name,
|
|
1636
1690
|
description,
|
|
1637
1691
|
capabilities,
|
|
1638
|
-
|
|
1692
|
+
priceSubunits: BigInt(Math.round(priceSubunits)),
|
|
1693
|
+
asset,
|
|
1639
1694
|
skillDir,
|
|
1640
1695
|
systemPrompt,
|
|
1641
1696
|
tools,
|
|
@@ -1679,7 +1734,8 @@ function loadSkillsFromDir(skillsDir) {
|
|
|
1679
1734
|
parsed.name,
|
|
1680
1735
|
parsed.description,
|
|
1681
1736
|
parsed.capabilities,
|
|
1682
|
-
Number(parsed.
|
|
1737
|
+
Number(parsed.priceSubunits),
|
|
1738
|
+
parsed.asset,
|
|
1683
1739
|
parsed.image,
|
|
1684
1740
|
parsed.imageFile,
|
|
1685
1741
|
entryPath,
|
|
@@ -1977,11 +2033,11 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
1977
2033
|
const registry = new SkillRegistry();
|
|
1978
2034
|
for (const skill of allSkills) {
|
|
1979
2035
|
registry.register(skill);
|
|
1980
|
-
const price = skill.
|
|
2036
|
+
const price = skill.priceSubunits > 0 ? formatAssetAmount(skill.asset, BigInt(skill.priceSubunits)) : "free";
|
|
1981
2037
|
console.log(` * Skill: ${skill.name} [${skill.capabilities.join(", ")}] - ${price}`);
|
|
1982
2038
|
}
|
|
1983
2039
|
console.log();
|
|
1984
|
-
const hasPaid = allSkills.some((s) => s.
|
|
2040
|
+
const hasPaid = allSkills.some((s) => s.priceSubunits > 0);
|
|
1985
2041
|
if (hasPaid && !solanaAddress) {
|
|
1986
2042
|
console.error(" ! Paid skills require a Solana address. Run `elisym init` to configure.\n");
|
|
1987
2043
|
process.exit(1);
|
|
@@ -2081,7 +2137,11 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2081
2137
|
chain: "solana",
|
|
2082
2138
|
network: walletNetwork,
|
|
2083
2139
|
address: solanaAddress,
|
|
2084
|
-
job_price: skill.
|
|
2140
|
+
job_price: skill.priceSubunits,
|
|
2141
|
+
token: skill.asset.token,
|
|
2142
|
+
...skill.asset.mint ? { mint: skill.asset.mint } : {},
|
|
2143
|
+
decimals: skill.asset.decimals,
|
|
2144
|
+
symbol: skill.asset.symbol
|
|
2085
2145
|
} : void 0
|
|
2086
2146
|
};
|
|
2087
2147
|
}
|
|
@@ -2338,6 +2398,30 @@ async function loadAgentWithPrompt(name, cwd) {
|
|
|
2338
2398
|
}
|
|
2339
2399
|
throw new Error("Unreachable");
|
|
2340
2400
|
}
|
|
2401
|
+
async function fetchUsdcBalance(rpc, owner) {
|
|
2402
|
+
const mint = USDC_SOLANA_DEVNET.mint;
|
|
2403
|
+
if (!mint) {
|
|
2404
|
+
return 0n;
|
|
2405
|
+
}
|
|
2406
|
+
try {
|
|
2407
|
+
const response = await rpc.getTokenAccountsByOwner(
|
|
2408
|
+
owner,
|
|
2409
|
+
{ mint: address(mint) },
|
|
2410
|
+
{ encoding: "jsonParsed", commitment: "confirmed" }
|
|
2411
|
+
).send();
|
|
2412
|
+
let total = 0n;
|
|
2413
|
+
for (const entry of response.value) {
|
|
2414
|
+
const parsed = entry.account.data;
|
|
2415
|
+
const raw = parsed?.parsed?.info?.tokenAmount?.amount;
|
|
2416
|
+
if (typeof raw === "string") {
|
|
2417
|
+
total += BigInt(raw);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return total;
|
|
2421
|
+
} catch {
|
|
2422
|
+
return 0n;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2341
2425
|
async function cmdWallet(name) {
|
|
2342
2426
|
const cwd = process.cwd();
|
|
2343
2427
|
if (!name) {
|
|
@@ -2371,11 +2455,13 @@ async function cmdWallet(name) {
|
|
|
2371
2455
|
const rpc = createSolanaRpc(rpcUrl);
|
|
2372
2456
|
const walletAddress = address(solPayment.address);
|
|
2373
2457
|
const { value: balance } = await rpc.getBalance(walletAddress).send();
|
|
2458
|
+
const usdcBalance = await fetchUsdcBalance(rpc, walletAddress);
|
|
2374
2459
|
console.log(`
|
|
2375
2460
|
Agent: ${name}`);
|
|
2376
2461
|
console.log(` Network: ${solPayment.network}`);
|
|
2377
2462
|
console.log(` Address: ${solPayment.address}`);
|
|
2378
|
-
console.log(`
|
|
2463
|
+
console.log(` SOL balance: ${formatSol(Number(balance))} (${balance} lamports)`);
|
|
2464
|
+
console.log(` USDC balance: ${formatAssetAmount(USDC_SOLANA_DEVNET, usdcBalance)}
|
|
2379
2465
|
`);
|
|
2380
2466
|
}
|
|
2381
2467
|
function readPackageVersion() {
|