@elisym/cli 0.24.0 → 0.25.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 +15 -12
- package/dist/index.js +633 -51
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ReadableStream } from 'node:stream/web';
|
|
3
3
|
import { readFileSync, existsSync, readdirSync, statSync, renameSync, chmodSync, mkdirSync, writeFileSync, appendFileSync, unlinkSync, rmdirSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve, basename, extname, relative, sep } from 'node:path';
|
|
5
|
-
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, signerFromSecretKeyBase58, ElisymClient, POLICY_D_TAG_PREFIX, KIND_LONG_FORM_ARTICLE, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, createBlossomTransport, generateSolanaWallet, getProtocolConfig, getProtocolProgramId, calculateProtocolFee, LIMITS, makeCensor, DEFAULT_REDACT_PATHS, POLICY_T_TAG, SESSION_ID_REGEX, createSlidingWindowLimiter, utf8ByteLength, readAcceptedTransports, decodeJobPayload, BoundedSet, KIND_JOB_FEEDBACK, encodeSecretKeyBase58, NATIVE_SOL } from '@elisym/sdk';
|
|
5
|
+
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, signerFromSecretKeyBase58, ElisymClient, KIND_EXTERNAL_IDENTITIES, POLICY_D_TAG_PREFIX, KIND_LONG_FORM_ARTICLE, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, createBlossomTransport, verifyAgentIdentities, generateSolanaWallet, getProtocolConfig, getProtocolProgramId, calculateProtocolFee, LIMITS, makeCensor, DEFAULT_REDACT_PATHS, POLICY_T_TAG, SESSION_ID_REGEX, createSlidingWindowLimiter, utf8ByteLength, readAcceptedTransports, decodeJobPayload, BoundedSet, KIND_JOB_FEEDBACK, GITHUB_USERNAME_REGEX, X_USERNAME_REGEX, normalizeNip05Identifier, splitNip05Identifier, encodeSecretKeyBase58, NATIVE_SOL, GIST_ID_REGEX, TWEET_ID_REGEX } from '@elisym/sdk';
|
|
6
6
|
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, loadPoliciesFromDir, ensureGitignoreHasIrohEntry, ensureGitignoreHasX402Entries, ensureGitignoreHasSessionsEntry, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
7
7
|
import { isAddress, createSolanaRpc, address } from '@solana/kit';
|
|
8
8
|
import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
|
|
@@ -52,9 +52,9 @@ function sleepWithSignal(ms, signal) {
|
|
|
52
52
|
return Promise.reject(createAbortError());
|
|
53
53
|
}
|
|
54
54
|
if (!signal) {
|
|
55
|
-
return new Promise((
|
|
55
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
56
56
|
}
|
|
57
|
-
return new Promise((
|
|
57
|
+
return new Promise((resolve4, reject) => {
|
|
58
58
|
const cleanup = () => {
|
|
59
59
|
clearTimeout(timer);
|
|
60
60
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -65,7 +65,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
65
65
|
};
|
|
66
66
|
const timer = setTimeout(() => {
|
|
67
67
|
cleanup();
|
|
68
|
-
|
|
68
|
+
resolve4();
|
|
69
69
|
}, ms);
|
|
70
70
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
71
71
|
});
|
|
@@ -1442,6 +1442,538 @@ var init_init = __esm({
|
|
|
1442
1442
|
init_llm();
|
|
1443
1443
|
}
|
|
1444
1444
|
});
|
|
1445
|
+
var IDENTITY_PLATFORMS = ["github", "x", "website"];
|
|
1446
|
+
var X_PROOF_URL_HOSTS = /* @__PURE__ */ new Set([
|
|
1447
|
+
"x.com",
|
|
1448
|
+
"www.x.com",
|
|
1449
|
+
"mobile.x.com",
|
|
1450
|
+
"twitter.com",
|
|
1451
|
+
"www.twitter.com",
|
|
1452
|
+
"mobile.twitter.com"
|
|
1453
|
+
]);
|
|
1454
|
+
function githubProofText(npub) {
|
|
1455
|
+
return `Verifying that I control the following Nostr public key: ${npub}`;
|
|
1456
|
+
}
|
|
1457
|
+
function xProofText(npub) {
|
|
1458
|
+
return `Verifying my account on nostr My Public Key: "${npub}"`;
|
|
1459
|
+
}
|
|
1460
|
+
function websiteNostrJson(local, pubkeyHex) {
|
|
1461
|
+
return JSON.stringify({ names: { [local]: pubkeyHex } }, null, 2);
|
|
1462
|
+
}
|
|
1463
|
+
function parseIdentityPlatform(raw) {
|
|
1464
|
+
const lowered = raw.trim().toLowerCase();
|
|
1465
|
+
const match = IDENTITY_PLATFORMS.find((platform) => platform === lowered);
|
|
1466
|
+
if (match === void 0) {
|
|
1467
|
+
throw new Error(`Unknown platform "${raw}". Expected github, x, or website.`);
|
|
1468
|
+
}
|
|
1469
|
+
return match;
|
|
1470
|
+
}
|
|
1471
|
+
function parseGithubProofInput(raw) {
|
|
1472
|
+
const trimmed = raw.trim();
|
|
1473
|
+
const bareId = trimmed.toLowerCase();
|
|
1474
|
+
if (GIST_ID_REGEX.test(bareId)) {
|
|
1475
|
+
return { proofId: bareId };
|
|
1476
|
+
}
|
|
1477
|
+
let url;
|
|
1478
|
+
try {
|
|
1479
|
+
url = new URL(trimmed);
|
|
1480
|
+
} catch {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "gist.github.com") {
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
|
|
1487
|
+
const [userSegment, gistSegment] = segments;
|
|
1488
|
+
if (userSegment === void 0 || gistSegment === void 0) {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
const username = userSegment.toLowerCase();
|
|
1492
|
+
const gistId = gistSegment.toLowerCase();
|
|
1493
|
+
if (!GITHUB_USERNAME_REGEX.test(username) || !GIST_ID_REGEX.test(gistId)) {
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
return { proofId: gistId, username };
|
|
1497
|
+
}
|
|
1498
|
+
function parseXProofInput(raw) {
|
|
1499
|
+
const trimmed = raw.trim();
|
|
1500
|
+
if (TWEET_ID_REGEX.test(trimmed)) {
|
|
1501
|
+
return { proofId: trimmed };
|
|
1502
|
+
}
|
|
1503
|
+
let url;
|
|
1504
|
+
try {
|
|
1505
|
+
url = new URL(trimmed);
|
|
1506
|
+
} catch {
|
|
1507
|
+
return null;
|
|
1508
|
+
}
|
|
1509
|
+
if (url.protocol !== "https:" || !X_PROOF_URL_HOSTS.has(url.hostname.toLowerCase())) {
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
|
|
1513
|
+
const [userSegment, statusLiteral, idSegment] = segments;
|
|
1514
|
+
if (userSegment === void 0 || statusLiteral !== "status" || idSegment === void 0) {
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
const username = userSegment.toLowerCase();
|
|
1518
|
+
if (!X_USERNAME_REGEX.test(username) || !TWEET_ID_REGEX.test(idSegment)) {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
return { proofId: idSegment, username };
|
|
1522
|
+
}
|
|
1523
|
+
function buildIdentityClaimInputs(identities) {
|
|
1524
|
+
const claims = [];
|
|
1525
|
+
if (identities?.github !== void 0) {
|
|
1526
|
+
claims.push({
|
|
1527
|
+
platform: "github",
|
|
1528
|
+
handle: identities.github.username,
|
|
1529
|
+
proofId: identities.github.gist
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
if (identities?.x !== void 0) {
|
|
1533
|
+
claims.push({ platform: "x", handle: identities.x.username, proofId: identities.x.tweet });
|
|
1534
|
+
}
|
|
1535
|
+
return claims;
|
|
1536
|
+
}
|
|
1537
|
+
function identitiesFromYaml(identities) {
|
|
1538
|
+
const claims = [];
|
|
1539
|
+
if (identities === void 0) {
|
|
1540
|
+
return claims;
|
|
1541
|
+
}
|
|
1542
|
+
if (identities.github !== void 0) {
|
|
1543
|
+
claims.push({
|
|
1544
|
+
platform: "github",
|
|
1545
|
+
handle: identities.github.username,
|
|
1546
|
+
proofUrl: `https://gist.github.com/${encodeURIComponent(identities.github.username)}/${encodeURIComponent(identities.github.gist)}`
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
if (identities.x !== void 0) {
|
|
1550
|
+
claims.push({
|
|
1551
|
+
platform: "x",
|
|
1552
|
+
handle: identities.x.username,
|
|
1553
|
+
proofUrl: `https://x.com/${encodeURIComponent(identities.x.username)}/status/${encodeURIComponent(identities.x.tweet)}`
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
if (identities.website !== void 0) {
|
|
1557
|
+
const identifier = normalizeNip05Identifier(identities.website);
|
|
1558
|
+
if (identifier !== null) {
|
|
1559
|
+
const { domain } = splitNip05Identifier(identifier);
|
|
1560
|
+
claims.push({ platform: "website", handle: identifier, proofUrl: `https://${domain}` });
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return claims;
|
|
1564
|
+
}
|
|
1565
|
+
async function createInquirerPrompter() {
|
|
1566
|
+
const { default: inquirer } = await import('inquirer');
|
|
1567
|
+
return {
|
|
1568
|
+
async input(message) {
|
|
1569
|
+
const { value } = await inquirer.prompt([{ type: "input", name: "value", message }]);
|
|
1570
|
+
return String(value);
|
|
1571
|
+
},
|
|
1572
|
+
async confirm(message, defaultValue) {
|
|
1573
|
+
const { value } = await inquirer.prompt([
|
|
1574
|
+
{ type: "confirm", name: "value", message, default: defaultValue }
|
|
1575
|
+
]);
|
|
1576
|
+
return Boolean(value);
|
|
1577
|
+
},
|
|
1578
|
+
async select(message, choices) {
|
|
1579
|
+
const { value } = await inquirer.prompt([{ type: "list", name: "value", message, choices }]);
|
|
1580
|
+
return String(value);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function createDefaultPublisher(relays) {
|
|
1585
|
+
const client = new ElisymClient({ relays });
|
|
1586
|
+
return {
|
|
1587
|
+
fetchExternalIdentityClaims: (pubkey) => client.discovery.fetchExternalIdentityClaims(pubkey),
|
|
1588
|
+
publishExternalIdentities: (identity2, claims) => client.discovery.publishExternalIdentities(identity2, claims),
|
|
1589
|
+
publishProfile: (identity2, name, about, picture, banner, nip05) => client.discovery.publishProfile(identity2, name, about, picture, banner, nip05),
|
|
1590
|
+
close: () => client.close()
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
async function resolveAndLoadAgent(nameArg, cwd, prompter) {
|
|
1594
|
+
let name = nameArg;
|
|
1595
|
+
if (!name) {
|
|
1596
|
+
const agents = await listAgents(cwd);
|
|
1597
|
+
if (agents.length === 0) {
|
|
1598
|
+
throw new Error("No agents found. Run `npx @elisym/cli init` first.");
|
|
1599
|
+
}
|
|
1600
|
+
name = await prompter.select(
|
|
1601
|
+
"Select agent:",
|
|
1602
|
+
agents.map((agent) => ({ name: `${agent.name} (${agent.source})`, value: agent.name }))
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
1606
|
+
const loaded = await loadAgent(name, cwd, passphrase);
|
|
1607
|
+
return { name, loaded };
|
|
1608
|
+
}
|
|
1609
|
+
function relaysFor(yaml) {
|
|
1610
|
+
return yaml.relays.length > 0 ? yaml.relays : [...RELAYS];
|
|
1611
|
+
}
|
|
1612
|
+
async function resolvePreservedMediaUrl(configured, agentDir, cache, publishedUrl) {
|
|
1613
|
+
if (!configured) {
|
|
1614
|
+
return void 0;
|
|
1615
|
+
}
|
|
1616
|
+
if (/^https?:\/\//i.test(configured)) {
|
|
1617
|
+
return configured;
|
|
1618
|
+
}
|
|
1619
|
+
const agentRoot = resolve(agentDir);
|
|
1620
|
+
const candidate = resolve(agentRoot, configured);
|
|
1621
|
+
const rel = relative(agentRoot, candidate);
|
|
1622
|
+
if (rel === "" || rel.startsWith("..") || rel.includes(`..${sep}`)) {
|
|
1623
|
+
return publishedUrl;
|
|
1624
|
+
}
|
|
1625
|
+
const cached = await lookupCachedUrl(cache, configured, candidate);
|
|
1626
|
+
return cached ?? publishedUrl;
|
|
1627
|
+
}
|
|
1628
|
+
async function republishAgentProfile(publisher, identity2, agentName, agentDir, yaml) {
|
|
1629
|
+
let publishedProfile;
|
|
1630
|
+
try {
|
|
1631
|
+
const published = await publisher.fetchExternalIdentityClaims(identity2.publicKey);
|
|
1632
|
+
publishedProfile = published.profile;
|
|
1633
|
+
} catch {
|
|
1634
|
+
}
|
|
1635
|
+
const mediaCache = await readMediaCache(agentDir);
|
|
1636
|
+
const picture = await resolvePreservedMediaUrl(
|
|
1637
|
+
yaml.picture,
|
|
1638
|
+
agentDir,
|
|
1639
|
+
mediaCache,
|
|
1640
|
+
publishedProfile?.picture
|
|
1641
|
+
);
|
|
1642
|
+
const banner = await resolvePreservedMediaUrl(
|
|
1643
|
+
yaml.banner,
|
|
1644
|
+
agentDir,
|
|
1645
|
+
mediaCache,
|
|
1646
|
+
publishedProfile?.banner
|
|
1647
|
+
);
|
|
1648
|
+
const unresolved = [];
|
|
1649
|
+
if (yaml.picture && picture === void 0) {
|
|
1650
|
+
unresolved.push("picture");
|
|
1651
|
+
}
|
|
1652
|
+
if (yaml.banner && banner === void 0) {
|
|
1653
|
+
unresolved.push("banner");
|
|
1654
|
+
}
|
|
1655
|
+
if (unresolved.length > 0) {
|
|
1656
|
+
throw new Error(
|
|
1657
|
+
`${unresolved.join(" and ")} carry-over unavailable (no media cache entry and no currently-published URL - the relay read may have failed), so republishing kind 0 would wipe it from the profile`
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
await publisher.publishProfile(
|
|
1661
|
+
identity2,
|
|
1662
|
+
yaml.display_name ?? agentName,
|
|
1663
|
+
yaml.description ?? "",
|
|
1664
|
+
picture,
|
|
1665
|
+
banner,
|
|
1666
|
+
yaml.identities?.website
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
async function sweepStaleIdentityClaims(discovery, identity2) {
|
|
1670
|
+
try {
|
|
1671
|
+
const published = await discovery.fetchExternalIdentityClaims(identity2.publicKey);
|
|
1672
|
+
const staleClaims = published.identities.filter((claim) => claim.platform !== "website");
|
|
1673
|
+
if (staleClaims.length === 0) {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
await discovery.publishExternalIdentities(identity2, []);
|
|
1677
|
+
return true;
|
|
1678
|
+
} catch {
|
|
1679
|
+
return false;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
async function promptGithubLink(loaded, npub, prompter) {
|
|
1683
|
+
console.log(
|
|
1684
|
+
"\n 1. Create a PUBLIC gist (https://gist.github.com/new) containing ONLY this\n single line (the proof statement - a standard NIP-39 text that any\n Nostr client can verify; do not paste these instructions):"
|
|
1685
|
+
);
|
|
1686
|
+
console.log(`
|
|
1687
|
+
${githubProofText(npub)}
|
|
1688
|
+
`);
|
|
1689
|
+
console.log(" 2. Paste the gist URL (or its id) below.\n");
|
|
1690
|
+
const rawProof = await prompter.input("Gist URL or id:");
|
|
1691
|
+
const parsed = parseGithubProofInput(rawProof);
|
|
1692
|
+
if (parsed === null) {
|
|
1693
|
+
throw new Error("Expected a gist URL (https://gist.github.com/<user>/<id>) or a hex gist id.");
|
|
1694
|
+
}
|
|
1695
|
+
let username = parsed.username;
|
|
1696
|
+
if (username === void 0) {
|
|
1697
|
+
const rawUsername = await prompter.input("GitHub username:");
|
|
1698
|
+
username = rawUsername.trim().toLowerCase();
|
|
1699
|
+
}
|
|
1700
|
+
if (!GITHUB_USERNAME_REGEX.test(username)) {
|
|
1701
|
+
throw new Error(`Invalid GitHub username: "${username}" (alphanumeric or hyphen, 1-39 chars).`);
|
|
1702
|
+
}
|
|
1703
|
+
return {
|
|
1704
|
+
nextIdentities: {
|
|
1705
|
+
...loaded.yaml.identities,
|
|
1706
|
+
github: { username, gist: parsed.proofId }
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
async function promptXLink(loaded, npub, prompter) {
|
|
1711
|
+
console.log(
|
|
1712
|
+
"\n 1. Post a tweet whose text is ONLY this line, including the double quotes\n (the proof statement - a standard NIP-39 text; the account you post\n from becomes the linked identity):"
|
|
1713
|
+
);
|
|
1714
|
+
console.log(`
|
|
1715
|
+
${xProofText(npub)}
|
|
1716
|
+
`);
|
|
1717
|
+
console.log(" 2. Paste the tweet URL (or its status id) below.\n");
|
|
1718
|
+
const rawProof = await prompter.input("Tweet URL or id:");
|
|
1719
|
+
const parsed = parseXProofInput(rawProof);
|
|
1720
|
+
if (parsed === null) {
|
|
1721
|
+
throw new Error(
|
|
1722
|
+
"Expected a tweet URL (https://x.com/<user>/status/<id>) or a numeric status id."
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
let username = parsed.username;
|
|
1726
|
+
if (username === void 0) {
|
|
1727
|
+
const rawUsername = await prompter.input("X username (without @):");
|
|
1728
|
+
username = rawUsername.trim().replace(/^@/, "").toLowerCase();
|
|
1729
|
+
}
|
|
1730
|
+
if (!X_USERNAME_REGEX.test(username)) {
|
|
1731
|
+
throw new Error(`Invalid X username: "${username}" (alphanumeric or underscore, 1-15 chars).`);
|
|
1732
|
+
}
|
|
1733
|
+
return {
|
|
1734
|
+
nextIdentities: {
|
|
1735
|
+
...loaded.yaml.identities,
|
|
1736
|
+
x: { username, tweet: parsed.proofId }
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
async function promptWebsiteLink(loaded, pubkeyHex, prompter) {
|
|
1741
|
+
const rawIdentifier = await prompter.input("NIP-05 identifier (name@domain or bare domain):");
|
|
1742
|
+
const identifier = normalizeNip05Identifier(rawIdentifier.trim());
|
|
1743
|
+
if (identifier === null) {
|
|
1744
|
+
throw new Error(
|
|
1745
|
+
`Invalid NIP-05 identifier: "${rawIdentifier.trim()}". Expected name@domain or a bare domain (ASCII hostname labels, no IP literals).`
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
const { local, domain } = splitNip05Identifier(identifier);
|
|
1749
|
+
console.log(`
|
|
1750
|
+
Serve this JSON at https://${domain}/.well-known/nostr.json :
|
|
1751
|
+
`);
|
|
1752
|
+
for (const line of websiteNostrJson(local, pubkeyHex).split("\n")) {
|
|
1753
|
+
console.log(` ${line}`);
|
|
1754
|
+
}
|
|
1755
|
+
console.log(
|
|
1756
|
+
`
|
|
1757
|
+
It must answer https://${domain}/.well-known/nostr.json?name=${local} over https,`
|
|
1758
|
+
);
|
|
1759
|
+
console.log(" without redirects - NIP-05 forbids following them. The verifier consults only");
|
|
1760
|
+
console.log(` this exact host (no www/apex twin fallback), so the file must live at ${domain}.`);
|
|
1761
|
+
console.log(" Sending `Access-Control-Allow-Origin: *` is recommended (NIP-05), so browser");
|
|
1762
|
+
console.log(" clients can verify it too.\n");
|
|
1763
|
+
const live = await prompter.confirm("Is the file deployed and reachable? Verify now", true);
|
|
1764
|
+
if (!live) {
|
|
1765
|
+
throw new Error(
|
|
1766
|
+
"aborted - deploy the file, then re-run `npx @elisym/cli identity link website`. Nothing was written."
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
return {
|
|
1770
|
+
nextIdentities: {
|
|
1771
|
+
...loaded.yaml.identities,
|
|
1772
|
+
website: identifier
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
function describeBrokenProof(platform, claim) {
|
|
1777
|
+
switch (platform) {
|
|
1778
|
+
case "github":
|
|
1779
|
+
return `the gist at ${claim.proofUrl} is missing, belongs to another user, or does not contain the exact proof text with this agent's npub`;
|
|
1780
|
+
case "x":
|
|
1781
|
+
return `the tweet at ${claim.proofUrl} is deleted, belongs to another account, or does not contain the exact proof text with this agent's npub`;
|
|
1782
|
+
case "website":
|
|
1783
|
+
return `${claim.proofUrl}/.well-known/nostr.json does not map "${claim.handle}" to this agent's pubkey`;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
async function cmdIdentityLink(platformArg, agentName, deps = {}) {
|
|
1787
|
+
const platform = parseIdentityPlatform(platformArg);
|
|
1788
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1789
|
+
const prompter = deps.prompter ?? await createInquirerPrompter();
|
|
1790
|
+
const verify = deps.verify ?? verifyAgentIdentities;
|
|
1791
|
+
const { name, loaded } = await resolveAndLoadAgent(agentName, cwd, prompter);
|
|
1792
|
+
const identity2 = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
1793
|
+
console.log(`
|
|
1794
|
+
Linking ${platform} to agent "${name}" (${identity2.npub}).`);
|
|
1795
|
+
let prompted;
|
|
1796
|
+
if (platform === "github") {
|
|
1797
|
+
prompted = await promptGithubLink(loaded, identity2.npub, prompter);
|
|
1798
|
+
} else if (platform === "x") {
|
|
1799
|
+
prompted = await promptXLink(loaded, identity2.npub, prompter);
|
|
1800
|
+
} else {
|
|
1801
|
+
prompted = await promptWebsiteLink(loaded, identity2.publicKey, prompter);
|
|
1802
|
+
}
|
|
1803
|
+
const claim = identitiesFromYaml(prompted.nextIdentities).find(
|
|
1804
|
+
(candidate) => candidate.platform === platform
|
|
1805
|
+
);
|
|
1806
|
+
if (claim === void 0) {
|
|
1807
|
+
throw new Error("Internal: failed to build the identity claim from the prompt input.");
|
|
1808
|
+
}
|
|
1809
|
+
console.log("\n Verifying the proof (live, cache bypassed)...");
|
|
1810
|
+
const results = await verify(identity2.publicKey, [claim], { bypassCache: true });
|
|
1811
|
+
const result = results[0];
|
|
1812
|
+
if (result === void 0) {
|
|
1813
|
+
throw new Error("Internal: the verifier returned no result.");
|
|
1814
|
+
}
|
|
1815
|
+
if (result.status === "broken") {
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
`proof check failed: ${describeBrokenProof(platform, claim)}. Nothing was written.`
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
if (result.status === "unverifiable") {
|
|
1821
|
+
console.warn(
|
|
1822
|
+
" ! Could not check the proof (network error, rate limit, or the proof is not reachable yet)."
|
|
1823
|
+
);
|
|
1824
|
+
console.warn(" ! This is neutral - the proof may be fine - but it was NOT verified.");
|
|
1825
|
+
const proceed = await prompter.confirm(
|
|
1826
|
+
"Link anyway (write elisym.yaml and publish the unverified claim)?",
|
|
1827
|
+
false
|
|
1828
|
+
);
|
|
1829
|
+
if (!proceed) {
|
|
1830
|
+
throw new Error("aborted - nothing was written.");
|
|
1831
|
+
}
|
|
1832
|
+
} else {
|
|
1833
|
+
console.log(" Proof verified.");
|
|
1834
|
+
}
|
|
1835
|
+
const nextYaml = { ...loaded.yaml, identities: prompted.nextIdentities };
|
|
1836
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
1837
|
+
console.log(` Wrote identities.${platform} to elisym.yaml.`);
|
|
1838
|
+
const createPublisher = deps.createPublisher ?? createDefaultPublisher;
|
|
1839
|
+
const publisher = createPublisher(relaysFor(loaded.yaml));
|
|
1840
|
+
try {
|
|
1841
|
+
const claims = buildIdentityClaimInputs(prompted.nextIdentities);
|
|
1842
|
+
try {
|
|
1843
|
+
await publisher.publishExternalIdentities(identity2, claims);
|
|
1844
|
+
console.log(` Published identity claims (kind 10011, ${claims.length} tag(s)).`);
|
|
1845
|
+
if (platform === "website") {
|
|
1846
|
+
await republishAgentProfile(publisher, identity2, name, loaded.dir, nextYaml);
|
|
1847
|
+
console.log(" Republished profile (kind 0) with nip05.");
|
|
1848
|
+
}
|
|
1849
|
+
} catch (error) {
|
|
1850
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1851
|
+
throw new Error(
|
|
1852
|
+
`linked locally, but publishing failed: ${message}. Run \`npx @elisym/cli start ${name}\` to publish, or re-run this command.`
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
} finally {
|
|
1856
|
+
publisher.close();
|
|
1857
|
+
}
|
|
1858
|
+
console.log(`
|
|
1859
|
+
Linked ${platform} to agent "${name}".
|
|
1860
|
+
`);
|
|
1861
|
+
}
|
|
1862
|
+
function removeIdentityEntry(identities, platform) {
|
|
1863
|
+
const next = { ...identities };
|
|
1864
|
+
delete next[platform];
|
|
1865
|
+
const hasEntries = next.github !== void 0 || next.x !== void 0 || next.website !== void 0;
|
|
1866
|
+
return hasEntries ? next : void 0;
|
|
1867
|
+
}
|
|
1868
|
+
async function cmdIdentityUnlink(platformArg, agentName, deps = {}) {
|
|
1869
|
+
const platform = parseIdentityPlatform(platformArg);
|
|
1870
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1871
|
+
const prompter = deps.prompter ?? await createInquirerPrompter();
|
|
1872
|
+
const { name, loaded } = await resolveAndLoadAgent(agentName, cwd, prompter);
|
|
1873
|
+
const identity2 = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
1874
|
+
const current = loaded.yaml.identities;
|
|
1875
|
+
if (current === void 0 || current[platform] === void 0) {
|
|
1876
|
+
console.log(`
|
|
1877
|
+
${platform} is not linked for agent "${name}" - nothing to unlink.
|
|
1878
|
+
`);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
const nextIdentities = removeIdentityEntry(current, platform);
|
|
1882
|
+
const nextYaml = { ...loaded.yaml };
|
|
1883
|
+
if (nextIdentities === void 0) {
|
|
1884
|
+
delete nextYaml.identities;
|
|
1885
|
+
} else {
|
|
1886
|
+
nextYaml.identities = nextIdentities;
|
|
1887
|
+
}
|
|
1888
|
+
await writeYaml(loaded.dir, nextYaml);
|
|
1889
|
+
console.log(`
|
|
1890
|
+
Removed identities.${platform} from elisym.yaml.`);
|
|
1891
|
+
const createPublisher = deps.createPublisher ?? createDefaultPublisher;
|
|
1892
|
+
const publisher = createPublisher(relaysFor(loaded.yaml));
|
|
1893
|
+
try {
|
|
1894
|
+
const remaining = buildIdentityClaimInputs(nextIdentities);
|
|
1895
|
+
try {
|
|
1896
|
+
await publisher.publishExternalIdentities(identity2, remaining);
|
|
1897
|
+
console.log(
|
|
1898
|
+
` Republished identity claims (kind 10011, ${remaining.length} tag(s) remaining).`
|
|
1899
|
+
);
|
|
1900
|
+
if (platform === "website") {
|
|
1901
|
+
await republishAgentProfile(publisher, identity2, name, loaded.dir, nextYaml);
|
|
1902
|
+
console.log(" Republished profile (kind 0) without nip05.");
|
|
1903
|
+
}
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1906
|
+
throw new Error(
|
|
1907
|
+
`unlinked locally, but publishing the retraction failed: ${message}. Run \`npx @elisym/cli start ${name}\` to propagate it.`
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
} finally {
|
|
1911
|
+
publisher.close();
|
|
1912
|
+
}
|
|
1913
|
+
console.log(`
|
|
1914
|
+
Unlinked ${platform} from agent "${name}".
|
|
1915
|
+
`);
|
|
1916
|
+
}
|
|
1917
|
+
function sameClaim(left, right) {
|
|
1918
|
+
return left.platform === right.platform && left.handle.toLowerCase() === right.handle.toLowerCase() && left.proofUrl.toLowerCase() === right.proofUrl.toLowerCase();
|
|
1919
|
+
}
|
|
1920
|
+
function buildStatusRows(results, publishedClaims) {
|
|
1921
|
+
return results.map((result) => ({
|
|
1922
|
+
platform: result.identity.platform,
|
|
1923
|
+
handle: result.identity.handle,
|
|
1924
|
+
proofUrl: result.identity.proofUrl,
|
|
1925
|
+
status: result.status,
|
|
1926
|
+
published: publishedClaims === void 0 ? void 0 : publishedClaims.some((claim) => sameClaim(claim, result.identity))
|
|
1927
|
+
}));
|
|
1928
|
+
}
|
|
1929
|
+
async function cmdIdentityStatus(agentName, deps = {}) {
|
|
1930
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
1931
|
+
const prompter = deps.prompter ?? await createInquirerPrompter();
|
|
1932
|
+
const verify = deps.verify ?? verifyAgentIdentities;
|
|
1933
|
+
const { name, loaded } = await resolveAndLoadAgent(agentName, cwd, prompter);
|
|
1934
|
+
const identity2 = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
1935
|
+
const localClaims = identitiesFromYaml(loaded.yaml.identities);
|
|
1936
|
+
const createPublisher = deps.createPublisher ?? createDefaultPublisher;
|
|
1937
|
+
const publisher = createPublisher(relaysFor(loaded.yaml));
|
|
1938
|
+
let published;
|
|
1939
|
+
try {
|
|
1940
|
+
published = await publisher.fetchExternalIdentityClaims(identity2.publicKey);
|
|
1941
|
+
} catch {
|
|
1942
|
+
console.warn(" ! Could not query the relays for published claims - drift check skipped.");
|
|
1943
|
+
} finally {
|
|
1944
|
+
publisher.close();
|
|
1945
|
+
}
|
|
1946
|
+
if (localClaims.length === 0) {
|
|
1947
|
+
console.log(`
|
|
1948
|
+
No identities linked in elisym.yaml for agent "${name}".`);
|
|
1949
|
+
console.log(" Link one: npx @elisym/cli identity link <github|x|website>\n");
|
|
1950
|
+
if (published !== void 0 && published.identities.length > 0) {
|
|
1951
|
+
console.log(
|
|
1952
|
+
` ! The relays still hold published identity claims - run \`npx @elisym/cli start ${name}\` to retract them.
|
|
1953
|
+
`
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
console.log(`
|
|
1959
|
+
Verifying ${localClaims.length} proof(s) (live, cache bypassed)...
|
|
1960
|
+
`);
|
|
1961
|
+
const results = await verify(identity2.publicKey, localClaims, { bypassCache: true });
|
|
1962
|
+
const rows = buildStatusRows(results, published?.identities);
|
|
1963
|
+
console.log(` Identities for agent "${name}" (${identity2.npub}):
|
|
1964
|
+
`);
|
|
1965
|
+
for (const row of rows) {
|
|
1966
|
+
const displayHandle = row.handle.startsWith("_@") ? row.handle.slice(2) : row.handle;
|
|
1967
|
+
console.log(` ${row.platform.padEnd(8)} ${row.status.padEnd(13)} ${displayHandle}`);
|
|
1968
|
+
console.log(` proof: ${row.proofUrl}`);
|
|
1969
|
+
if (row.published === false) {
|
|
1970
|
+
console.log(
|
|
1971
|
+
` ! linked locally but not published - run \`npx @elisym/cli start ${name}\` to publish`
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
console.log();
|
|
1976
|
+
}
|
|
1445
1977
|
|
|
1446
1978
|
// src/index.ts
|
|
1447
1979
|
init_init();
|
|
@@ -1696,7 +2228,7 @@ function parseRelayUrl(url) {
|
|
|
1696
2228
|
}
|
|
1697
2229
|
}
|
|
1698
2230
|
async function tcpProbe(host, port, timeoutMs) {
|
|
1699
|
-
return new Promise((
|
|
2231
|
+
return new Promise((resolve4) => {
|
|
1700
2232
|
const started = Date.now();
|
|
1701
2233
|
const socket = new Socket();
|
|
1702
2234
|
let settled = false;
|
|
@@ -1706,7 +2238,7 @@ async function tcpProbe(host, port, timeoutMs) {
|
|
|
1706
2238
|
}
|
|
1707
2239
|
settled = true;
|
|
1708
2240
|
socket.destroy();
|
|
1709
|
-
|
|
2241
|
+
resolve4(result);
|
|
1710
2242
|
};
|
|
1711
2243
|
const timer = setTimeout(() => finish({ error: "timeout" }), timeoutMs);
|
|
1712
2244
|
socket.once("connect", () => {
|
|
@@ -2203,8 +2735,8 @@ var SessionStore = class {
|
|
|
2203
2735
|
}
|
|
2204
2736
|
installLock(key) {
|
|
2205
2737
|
let release;
|
|
2206
|
-
const promise = new Promise((
|
|
2207
|
-
release =
|
|
2738
|
+
const promise = new Promise((resolve4) => {
|
|
2739
|
+
release = resolve4;
|
|
2208
2740
|
});
|
|
2209
2741
|
const entry = { promise, release };
|
|
2210
2742
|
this.locks.set(key, entry);
|
|
@@ -3008,7 +3540,7 @@ var PAID_GLOBAL_MAX_JOBS_PER_WINDOW = 2e3;
|
|
|
3008
3540
|
var MAX_TRACKED_CUSTOMERS = 1e3;
|
|
3009
3541
|
var GLOBAL_LIMITER_KEY = "__global__";
|
|
3010
3542
|
var AgentRuntime = class {
|
|
3011
|
-
constructor(transport, skills, skillCtx, config, ledger, callbacks = {}, healthMonitor, irohTransport,
|
|
3543
|
+
constructor(transport, skills, skillCtx, config, ledger, callbacks = {}, healthMonitor, irohTransport, identity2, blossomTransport, sessionStore) {
|
|
3012
3544
|
this.transport = transport;
|
|
3013
3545
|
this.skills = skills;
|
|
3014
3546
|
this.skillCtx = skillCtx;
|
|
@@ -3017,7 +3549,7 @@ var AgentRuntime = class {
|
|
|
3017
3549
|
this.callbacks = callbacks;
|
|
3018
3550
|
this.healthMonitor = healthMonitor;
|
|
3019
3551
|
this.irohTransport = irohTransport;
|
|
3020
|
-
this.identity =
|
|
3552
|
+
this.identity = identity2;
|
|
3021
3553
|
this.blossomTransport = blossomTransport;
|
|
3022
3554
|
this.sessionStore = sessionStore;
|
|
3023
3555
|
this.limit = pLimit(config.maxConcurrentJobs);
|
|
@@ -3410,8 +3942,8 @@ var AgentRuntime = class {
|
|
|
3410
3942
|
});
|
|
3411
3943
|
});
|
|
3412
3944
|
log("Agent runtime started. Listening for jobs...");
|
|
3413
|
-
await new Promise((
|
|
3414
|
-
this.abortController.signal.addEventListener("abort", () =>
|
|
3945
|
+
await new Promise((resolve4) => {
|
|
3946
|
+
this.abortController.signal.addEventListener("abort", () => resolve4(), { once: true });
|
|
3415
3947
|
let shuttingDown = false;
|
|
3416
3948
|
const onSignal = () => {
|
|
3417
3949
|
if (shuttingDown) {
|
|
@@ -4055,7 +4587,7 @@ var AgentRuntime = class {
|
|
|
4055
4587
|
let refOutcome;
|
|
4056
4588
|
let result;
|
|
4057
4589
|
try {
|
|
4058
|
-
result = await new Promise((
|
|
4590
|
+
result = await new Promise((resolve4, reject) => {
|
|
4059
4591
|
let settled = false;
|
|
4060
4592
|
let pending = 2;
|
|
4061
4593
|
let lastResult = { verified: false };
|
|
@@ -4065,7 +4597,7 @@ var AgentRuntime = class {
|
|
|
4065
4597
|
}
|
|
4066
4598
|
settled = true;
|
|
4067
4599
|
clearTimeout(deadline);
|
|
4068
|
-
|
|
4600
|
+
resolve4(verified);
|
|
4069
4601
|
};
|
|
4070
4602
|
const lose = (verified, reason) => {
|
|
4071
4603
|
if (settled) {
|
|
@@ -4077,7 +4609,7 @@ var AgentRuntime = class {
|
|
|
4077
4609
|
if (pending === 0) {
|
|
4078
4610
|
settled = true;
|
|
4079
4611
|
clearTimeout(deadline);
|
|
4080
|
-
|
|
4612
|
+
resolve4(lastResult);
|
|
4081
4613
|
}
|
|
4082
4614
|
};
|
|
4083
4615
|
this.transport.waitForPaymentSignature(job.jobId, job.customerId, verifyAbort.signal, sigPathTimeoutMs).then(async (sig) => {
|
|
@@ -4130,7 +4662,7 @@ var AgentRuntime = class {
|
|
|
4130
4662
|
}
|
|
4131
4663
|
settled = true;
|
|
4132
4664
|
log(`[${job.jobId.slice(0, 8)}] verify deadline (${deadlineMs}ms) hit`);
|
|
4133
|
-
|
|
4665
|
+
resolve4(lastResult);
|
|
4134
4666
|
}, deadlineMs);
|
|
4135
4667
|
if (signal?.aborted) {
|
|
4136
4668
|
settled = true;
|
|
@@ -5021,9 +5553,9 @@ function parseBidTag(value) {
|
|
|
5021
5553
|
}
|
|
5022
5554
|
var HEALTH_CHECK_IDLE_MS = 30 * 60 * 1e3;
|
|
5023
5555
|
var NostrTransport = class {
|
|
5024
|
-
constructor(client,
|
|
5556
|
+
constructor(client, identity2, kindOffsets) {
|
|
5025
5557
|
this.client = client;
|
|
5026
|
-
this.identity =
|
|
5558
|
+
this.identity = identity2;
|
|
5027
5559
|
this.kindOffsets = kindOffsets;
|
|
5028
5560
|
}
|
|
5029
5561
|
sub = null;
|
|
@@ -5101,7 +5633,7 @@ var NostrTransport = class {
|
|
|
5101
5633
|
* expiry window - without it this path waits forever on the relay.
|
|
5102
5634
|
*/
|
|
5103
5635
|
waitForPaymentSignature(jobId, customerPubkey, signal, timeoutMs) {
|
|
5104
|
-
return new Promise((
|
|
5636
|
+
return new Promise((resolve4) => {
|
|
5105
5637
|
let settled = false;
|
|
5106
5638
|
const filter = {
|
|
5107
5639
|
kinds: [KIND_JOB_FEEDBACK],
|
|
@@ -5120,7 +5652,7 @@ var NostrTransport = class {
|
|
|
5120
5652
|
}
|
|
5121
5653
|
signal.removeEventListener("abort", onAbort);
|
|
5122
5654
|
sub.close();
|
|
5123
|
-
|
|
5655
|
+
resolve4(sig);
|
|
5124
5656
|
};
|
|
5125
5657
|
const sub = this.client.pool.subscribe(filter, (event) => {
|
|
5126
5658
|
if (settled) {
|
|
@@ -5214,7 +5746,7 @@ var NostrTransport = class {
|
|
|
5214
5746
|
function startWatchdog(deps) {
|
|
5215
5747
|
const {
|
|
5216
5748
|
client,
|
|
5217
|
-
identity,
|
|
5749
|
+
identity: identity2,
|
|
5218
5750
|
transport,
|
|
5219
5751
|
onPing,
|
|
5220
5752
|
log,
|
|
@@ -5225,7 +5757,7 @@ function startWatchdog(deps) {
|
|
|
5225
5757
|
selfPingTimeoutMs = WATCHDOG_SELF_PING_TIMEOUT_MS,
|
|
5226
5758
|
now = Date.now
|
|
5227
5759
|
} = deps;
|
|
5228
|
-
let pingSub = client.ping.subscribeToPings(
|
|
5760
|
+
let pingSub = client.ping.subscribeToPings(identity2, onPing);
|
|
5229
5761
|
let stopped = false;
|
|
5230
5762
|
let probeBusy = false;
|
|
5231
5763
|
let selfPingBusy = false;
|
|
@@ -5234,7 +5766,7 @@ function startWatchdog(deps) {
|
|
|
5234
5766
|
const resetPoolAndResubscribe = () => {
|
|
5235
5767
|
transport.stop();
|
|
5236
5768
|
client.pool.reset();
|
|
5237
|
-
pingSub = client.ping.subscribeToPings(
|
|
5769
|
+
pingSub = client.ping.subscribeToPings(identity2, onPing);
|
|
5238
5770
|
transport.restart();
|
|
5239
5771
|
};
|
|
5240
5772
|
const detectAndHandleSleep = () => {
|
|
@@ -5290,7 +5822,7 @@ function startWatchdog(deps) {
|
|
|
5290
5822
|
}
|
|
5291
5823
|
selfPingBusy = true;
|
|
5292
5824
|
try {
|
|
5293
|
-
const result = await client.ping.pingAgent(
|
|
5825
|
+
const result = await client.ping.pingAgent(identity2.publicKey, selfPingTimeoutMs);
|
|
5294
5826
|
if (stopped || result.online) {
|
|
5295
5827
|
return;
|
|
5296
5828
|
}
|
|
@@ -5778,18 +6310,18 @@ async function capChallengeResponse(response) {
|
|
|
5778
6310
|
});
|
|
5779
6311
|
}
|
|
5780
6312
|
function abortableDelay(ms, signal) {
|
|
5781
|
-
return new Promise((
|
|
6313
|
+
return new Promise((resolve4) => {
|
|
5782
6314
|
if (signal?.aborted) {
|
|
5783
|
-
|
|
6315
|
+
resolve4(false);
|
|
5784
6316
|
return;
|
|
5785
6317
|
}
|
|
5786
6318
|
const onAbort = () => {
|
|
5787
6319
|
clearTimeout(timer);
|
|
5788
|
-
|
|
6320
|
+
resolve4(false);
|
|
5789
6321
|
};
|
|
5790
6322
|
const timer = setTimeout(() => {
|
|
5791
6323
|
signal?.removeEventListener("abort", onAbort);
|
|
5792
|
-
|
|
6324
|
+
resolve4(true);
|
|
5793
6325
|
}, ms);
|
|
5794
6326
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5795
6327
|
});
|
|
@@ -6418,7 +6950,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6418
6950
|
});
|
|
6419
6951
|
}
|
|
6420
6952
|
console.log(" Connecting to relays and publishing capabilities...");
|
|
6421
|
-
const
|
|
6953
|
+
const identity2 = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
6422
6954
|
const relays = loaded.yaml.relays.length > 0 ? loaded.yaml.relays : [...RELAYS];
|
|
6423
6955
|
const client = new ElisymClient({ relays });
|
|
6424
6956
|
if (process.env.ELISYM_NET_DIAG === "1") {
|
|
@@ -6440,7 +6972,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6440
6972
|
loaded.dir,
|
|
6441
6973
|
mediaCache,
|
|
6442
6974
|
client.blossom,
|
|
6443
|
-
|
|
6975
|
+
identity2,
|
|
6444
6976
|
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
6445
6977
|
);
|
|
6446
6978
|
const bannerUrl = await resolveMediaField(
|
|
@@ -6448,7 +6980,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6448
6980
|
loaded.dir,
|
|
6449
6981
|
mediaCache,
|
|
6450
6982
|
client.blossom,
|
|
6451
|
-
|
|
6983
|
+
identity2,
|
|
6452
6984
|
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
6453
6985
|
);
|
|
6454
6986
|
for (const skill of allSkills) {
|
|
@@ -6465,7 +6997,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6465
6997
|
skillRoot,
|
|
6466
6998
|
mediaCache,
|
|
6467
6999
|
client.blossom,
|
|
6468
|
-
|
|
7000
|
+
identity2,
|
|
6469
7001
|
() => mediaCacheDirty = true
|
|
6470
7002
|
);
|
|
6471
7003
|
if (url) {
|
|
@@ -6478,11 +7010,12 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6478
7010
|
let profilePublished = false;
|
|
6479
7011
|
try {
|
|
6480
7012
|
await client.discovery.publishProfile(
|
|
6481
|
-
|
|
7013
|
+
identity2,
|
|
6482
7014
|
loaded.yaml.display_name ?? agentName,
|
|
6483
7015
|
loaded.yaml.description ?? "",
|
|
6484
7016
|
pictureUrl,
|
|
6485
|
-
bannerUrl
|
|
7017
|
+
bannerUrl,
|
|
7018
|
+
loaded.yaml.identities?.website
|
|
6486
7019
|
);
|
|
6487
7020
|
profilePublished = true;
|
|
6488
7021
|
logger.debug({ event: "publish_ack", kind: 0 }, "profile published");
|
|
@@ -6490,6 +7023,32 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6490
7023
|
console.warn(` ! Failed to publish profile: ${e.message}`);
|
|
6491
7024
|
logger.warn({ event: "publish_failed", kind: 0, error: e.message }, "profile publish failed");
|
|
6492
7025
|
}
|
|
7026
|
+
if (loaded.yaml.identities !== void 0) {
|
|
7027
|
+
const identityClaims = buildIdentityClaimInputs(loaded.yaml.identities);
|
|
7028
|
+
try {
|
|
7029
|
+
await client.discovery.publishExternalIdentities(identity2, identityClaims);
|
|
7030
|
+
const summaryParts = identityClaims.map((claim) => `${claim.platform}:${claim.handle}`);
|
|
7031
|
+
if (loaded.yaml.identities.website !== void 0) {
|
|
7032
|
+
summaryParts.push(`website:${loaded.yaml.identities.website}`);
|
|
7033
|
+
}
|
|
7034
|
+
console.log(` * Identities: ${summaryParts.join(", ") || "none"}`);
|
|
7035
|
+
logger.debug(
|
|
7036
|
+
{ event: "publish_ack", kind: KIND_EXTERNAL_IDENTITIES, claims: identityClaims.length },
|
|
7037
|
+
"identity claims published"
|
|
7038
|
+
);
|
|
7039
|
+
} catch (e) {
|
|
7040
|
+
console.warn(` ! Failed to publish identity claims: ${e.message}`);
|
|
7041
|
+
logger.warn(
|
|
7042
|
+
{ event: "publish_failed", kind: KIND_EXTERNAL_IDENTITIES, error: e.message },
|
|
7043
|
+
"identity claims publish failed"
|
|
7044
|
+
);
|
|
7045
|
+
}
|
|
7046
|
+
} else {
|
|
7047
|
+
const sweptIdentities = await sweepStaleIdentityClaims(client.discovery, identity2);
|
|
7048
|
+
if (sweptIdentities) {
|
|
7049
|
+
console.log(" Removed stale identity claims (no identities in elisym.yaml).");
|
|
7050
|
+
}
|
|
7051
|
+
}
|
|
6493
7052
|
const policies = existsSync(paths.policies) ? loadPoliciesFromDir(paths.policies) : [];
|
|
6494
7053
|
const localPolicyDTags = new Set(
|
|
6495
7054
|
policies.map((policy) => `${POLICY_D_TAG_PREFIX}${policy.type}`)
|
|
@@ -6498,7 +7057,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6498
7057
|
try {
|
|
6499
7058
|
return await client.pool.querySync({
|
|
6500
7059
|
kinds: [KIND_LONG_FORM_ARTICLE],
|
|
6501
|
-
authors: [
|
|
7060
|
+
authors: [identity2.publicKey],
|
|
6502
7061
|
"#t": [POLICY_T_TAG]
|
|
6503
7062
|
});
|
|
6504
7063
|
} catch {
|
|
@@ -6525,7 +7084,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6525
7084
|
continue;
|
|
6526
7085
|
}
|
|
6527
7086
|
try {
|
|
6528
|
-
const { naddr } = await client.policies.publishPolicy(
|
|
7087
|
+
const { naddr } = await client.policies.publishPolicy(identity2, policy);
|
|
6529
7088
|
console.log(` * Policy: ${policy.type}@${policy.version} -> ${naddr}`);
|
|
6530
7089
|
logger.debug(
|
|
6531
7090
|
{ event: "publish_ack", kind: KIND_LONG_FORM_ARTICLE, policy: policy.type, naddr },
|
|
@@ -6557,7 +7116,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6557
7116
|
continue;
|
|
6558
7117
|
}
|
|
6559
7118
|
try {
|
|
6560
|
-
await client.policies.deletePolicy(
|
|
7119
|
+
await client.policies.deletePolicy(identity2, type);
|
|
6561
7120
|
console.log(` Removed stale policy: ${sanitizeForTerminal(type)}`);
|
|
6562
7121
|
} catch {
|
|
6563
7122
|
}
|
|
@@ -6620,7 +7179,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6620
7179
|
continue;
|
|
6621
7180
|
}
|
|
6622
7181
|
try {
|
|
6623
|
-
await client.discovery.publishCapability(
|
|
7182
|
+
await client.discovery.publishCapability(identity2, buildCard(skill), kinds);
|
|
6624
7183
|
cardsPublished += 1;
|
|
6625
7184
|
logger.debug(
|
|
6626
7185
|
{ event: "publish_ack", kind: 31990, skill: skill.name },
|
|
@@ -6637,7 +7196,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6637
7196
|
try {
|
|
6638
7197
|
const existingEvents = await client.pool.querySync({
|
|
6639
7198
|
kinds: [31990],
|
|
6640
|
-
authors: [
|
|
7199
|
+
authors: [identity2.publicKey],
|
|
6641
7200
|
"#t": ["elisym"]
|
|
6642
7201
|
});
|
|
6643
7202
|
const skillDTags = new Set(allSkills.map((s) => toDTag(s.name)));
|
|
@@ -6649,7 +7208,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6649
7208
|
try {
|
|
6650
7209
|
const card = JSON.parse(ev.content);
|
|
6651
7210
|
if (card.name && toDTag(card.name) === dTag) {
|
|
6652
|
-
await client.discovery.deleteCapability(
|
|
7211
|
+
await client.discovery.deleteCapability(identity2, card.name);
|
|
6653
7212
|
console.log(` Removed stale capability: ${sanitizeForTerminal(card.name)}`);
|
|
6654
7213
|
}
|
|
6655
7214
|
} catch {
|
|
@@ -6665,10 +7224,10 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6665
7224
|
console.log(` [debug] Relays: ${relays.length} configured (${relays.join(", ")})`);
|
|
6666
7225
|
}
|
|
6667
7226
|
const onPing = (senderPubkey, nonce) => {
|
|
6668
|
-
client.ping.sendPong(
|
|
7227
|
+
client.ping.sendPong(identity2, senderPubkey, nonce).catch(() => {
|
|
6669
7228
|
});
|
|
6670
7229
|
};
|
|
6671
|
-
const transport = new NostrTransport(client,
|
|
7230
|
+
const transport = new NostrTransport(client, identity2, [DEFAULT_KIND_OFFSET]);
|
|
6672
7231
|
const ledger = new JobLedger(paths.jobs);
|
|
6673
7232
|
const irohTransport = createIrohTransport({ storePath: join(loaded.dir, ".iroh") });
|
|
6674
7233
|
await ensureGitignoreHasIrohEntry(dirname(loaded.dir));
|
|
@@ -6709,7 +7268,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6709
7268
|
};
|
|
6710
7269
|
const watchdog = startWatchdog({
|
|
6711
7270
|
client,
|
|
6712
|
-
identity,
|
|
7271
|
+
identity: identity2,
|
|
6713
7272
|
transport,
|
|
6714
7273
|
onPing,
|
|
6715
7274
|
log: diagLog,
|
|
@@ -6723,7 +7282,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6723
7282
|
});
|
|
6724
7283
|
diagLog("LLM health monitor armed (lazy recovery, 5min interval).");
|
|
6725
7284
|
}
|
|
6726
|
-
const blossomTransport = createBlossomTransport({ blossom: client.blossom, identity });
|
|
7285
|
+
const blossomTransport = createBlossomTransport({ blossom: client.blossom, identity: identity2 });
|
|
6727
7286
|
const sessionStore = hasContextSkills ? new SessionStore(loaded.dir, diagLog) : void 0;
|
|
6728
7287
|
if (sessionStore) {
|
|
6729
7288
|
diagLog("Conversation sessions enabled (context: true skills present).");
|
|
@@ -6760,7 +7319,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
6760
7319
|
},
|
|
6761
7320
|
healthMonitor,
|
|
6762
7321
|
irohTransport,
|
|
6763
|
-
|
|
7322
|
+
identity2,
|
|
6764
7323
|
blossomTransport,
|
|
6765
7324
|
sessionStore
|
|
6766
7325
|
);
|
|
@@ -6798,7 +7357,7 @@ function isPublicSolanaRpcUrl(raw) {
|
|
|
6798
7357
|
function redactRpcUrlsInText(text) {
|
|
6799
7358
|
return text.replace(/https?:\/\/[^\s)'"]+/g, (url) => stripRpcSecrets(url));
|
|
6800
7359
|
}
|
|
6801
|
-
async function resolveMediaField(value, agentDir, cache, blossom,
|
|
7360
|
+
async function resolveMediaField(value, agentDir, cache, blossom, identity2, onCacheUpdate) {
|
|
6802
7361
|
if (!value) {
|
|
6803
7362
|
return void 0;
|
|
6804
7363
|
}
|
|
@@ -6816,7 +7375,7 @@ async function resolveMediaField(value, agentDir, cache, blossom, identity, onCa
|
|
|
6816
7375
|
agentDir,
|
|
6817
7376
|
cache,
|
|
6818
7377
|
blossom,
|
|
6819
|
-
|
|
7378
|
+
identity2,
|
|
6820
7379
|
() => onCacheUpdate(true)
|
|
6821
7380
|
);
|
|
6822
7381
|
}
|
|
@@ -6842,7 +7401,7 @@ function realpathInsideDir(absPath, rootDir) {
|
|
|
6842
7401
|
return null;
|
|
6843
7402
|
}
|
|
6844
7403
|
}
|
|
6845
|
-
async function uploadOrReuse(cacheKey, absPath, containRoot, cache, blossom,
|
|
7404
|
+
async function uploadOrReuse(cacheKey, absPath, containRoot, cache, blossom, identity2, onCacheUpdate) {
|
|
6846
7405
|
const realPath = realpathInsideDir(absPath, containRoot);
|
|
6847
7406
|
if (realPath === null) {
|
|
6848
7407
|
console.warn(
|
|
@@ -6859,7 +7418,7 @@ async function uploadOrReuse(cacheKey, absPath, containRoot, cache, blossom, ide
|
|
|
6859
7418
|
const data = readFileSync(realPath);
|
|
6860
7419
|
const sha256 = createHash("sha256").update(data).digest("hex");
|
|
6861
7420
|
const blob = new Blob([data], { type: mimeFromPath(absPath) });
|
|
6862
|
-
const descriptor = await blossom.upload(
|
|
7421
|
+
const descriptor = await blossom.upload(identity2, blob);
|
|
6863
7422
|
cache[cacheKey] = newCacheEntry(descriptor.url, sha256);
|
|
6864
7423
|
onCacheUpdate();
|
|
6865
7424
|
console.log(` Uploaded: ${descriptor.url}`);
|
|
@@ -7466,8 +8025,8 @@ program.command("list").description("List all agents (project-local and home-glo
|
|
|
7466
8025
|
for (const agent of agents) {
|
|
7467
8026
|
try {
|
|
7468
8027
|
const loaded = await loadAgent(agent.name, cwd);
|
|
7469
|
-
const
|
|
7470
|
-
const npub = nip19.npubEncode(
|
|
8028
|
+
const identity2 = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
8029
|
+
const npub = nip19.npubEncode(identity2.publicKey);
|
|
7471
8030
|
const solAddr = loaded.yaml.payments[0]?.address ? ` | Solana: ${loaded.yaml.payments[0].address}` : "";
|
|
7472
8031
|
const shadow = agent.shadowsGlobal ? " [shadows global]" : "";
|
|
7473
8032
|
console.log(` ${agent.name} (${agent.source})${shadow} | ${npub}${solAddr}`);
|
|
@@ -7480,6 +8039,29 @@ program.command("list").description("List all agents (project-local and home-glo
|
|
|
7480
8039
|
})
|
|
7481
8040
|
);
|
|
7482
8041
|
program.command("wallet [name]").description("Show wallet balance").action(safe(cmdWallet));
|
|
8042
|
+
var identity = program.command("identity").description("Link, verify, and unlink external identities (GitHub, X, website)");
|
|
8043
|
+
identity.command("link <platform> [agent]").description(
|
|
8044
|
+
"Link a github|x|website identity: prints the NIP-39 proof template, verifies the proof live, writes elisym.yaml, and publishes the claim (kind 10011 / kind-0 nip05)"
|
|
8045
|
+
).action(
|
|
8046
|
+
safe(async (platform, agent) => {
|
|
8047
|
+
await cmdIdentityLink(platform, agent);
|
|
8048
|
+
process.exit(0);
|
|
8049
|
+
})
|
|
8050
|
+
);
|
|
8051
|
+
identity.command("status [agent]").description(
|
|
8052
|
+
"List linked identities with live proof verification and published-claim drift check"
|
|
8053
|
+
).action(
|
|
8054
|
+
safe(async (agent) => {
|
|
8055
|
+
await cmdIdentityStatus(agent);
|
|
8056
|
+
process.exit(0);
|
|
8057
|
+
})
|
|
8058
|
+
);
|
|
8059
|
+
identity.command("unlink <platform> [agent]").description("Remove a github|x|website identity and retract the published claim").action(
|
|
8060
|
+
safe(async (platform, agent) => {
|
|
8061
|
+
await cmdIdentityUnlink(platform, agent);
|
|
8062
|
+
process.exit(0);
|
|
8063
|
+
})
|
|
8064
|
+
);
|
|
7483
8065
|
var x402 = program.command("x402").description("Bridge x402-paid HTTP services into elisym skills");
|
|
7484
8066
|
x402.command("add <url> [agent]").description("Generate a bridge skill from a live x402 endpoint (probes its 402 challenge)").option("--method <method>", "HTTP method for the upstream call: GET or POST (default POST)").option(
|
|
7485
8067
|
"--query-param <name>",
|