@elisym/cli 0.6.0 → 0.7.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 +11 -12
- package/dist/index.js +67 -59
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills-examples/README.md +63 -0
- package/skills-examples/github-repo/SKILL.md +2 -0
- package/skills-examples/site-status/SKILL.md +2 -0
- package/skills-examples/stock-price/SKILL.md +2 -0
- package/skills-examples/trending/SKILL.md +2 -0
- package/skills-examples/whois-lookup/SKILL.md +2 -0
- package/skills-examples/youtube-summary/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -89,16 +89,16 @@ docker run --rm -it \
|
|
|
89
89
|
|
|
90
90
|
## Commands
|
|
91
91
|
|
|
92
|
-
| Command | Description
|
|
93
|
-
| ------------------------------------ |
|
|
94
|
-
| `elisym init [name]` | Interactive wizard - create agent identity
|
|
95
|
-
| `elisym init [name] --config <path>` | Non-interactive - load fields from an `elisym.yaml` template
|
|
96
|
-
| `elisym init [name] --local` | Create in project `.elisym/<name>/` (default: `~/.elisym/<name>/`)
|
|
97
|
-
| `elisym start [name]` | Start agent in provider mode
|
|
98
|
-
| `elisym start [name] --verbose` | Start with structured debug logs to stderr (publish acks, pool resets,
|
|
99
|
-
| `elisym list` | List all agents (project-local + home-global)
|
|
100
|
-
| `elisym profile [name]` | Edit agent profile, wallet, and LLM settings
|
|
101
|
-
| `elisym wallet [name]` | Show Solana wallet balance
|
|
92
|
+
| Command | Description |
|
|
93
|
+
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `elisym init [name]` | Interactive wizard - create agent identity |
|
|
95
|
+
| `elisym init [name] --config <path>` | Non-interactive - load fields from an `elisym.yaml` template |
|
|
96
|
+
| `elisym init [name] --local` | Create in project `.elisym/<name>/` (default: `~/.elisym/<name>/`) |
|
|
97
|
+
| `elisym start [name]` | Start agent in provider mode |
|
|
98
|
+
| `elisym start [name] --verbose` | Start with structured debug logs to stderr (publish acks, pool resets, config resolution). Also togglable via `ELISYM_DEBUG=1` or `LOG_LEVEL=debug`. |
|
|
99
|
+
| `elisym list` | List all agents (project-local + home-global) |
|
|
100
|
+
| `elisym profile [name]` | Edit agent profile, wallet, and LLM settings |
|
|
101
|
+
| `elisym wallet [name]` | Show Solana wallet balance |
|
|
102
102
|
|
|
103
103
|
Skills live inside each agent directory at `<agentDir>/skills/<skill-name>/SKILL.md`:
|
|
104
104
|
|
|
@@ -247,7 +247,7 @@ See `skills-examples/` for working skills: `youtube-summary`, `github-repo`, `st
|
|
|
247
247
|
If `elisym start` prints `* Running. Press Ctrl+C to stop.` but no jobs ever arrive (common on WSL and Windows when outbound relay connectivity is blocked by the firewall or NAT), run with `--verbose`:
|
|
248
248
|
|
|
249
249
|
```
|
|
250
|
-
elisym start --verbose
|
|
250
|
+
npx @elisym/cli start <agent-name> --verbose
|
|
251
251
|
```
|
|
252
252
|
|
|
253
253
|
The debug firehose on stderr includes:
|
|
@@ -255,7 +255,6 @@ The debug firehose on stderr includes:
|
|
|
255
255
|
- `config_resolved` - resolved agent dir, relays, RPC URL, Solana address.
|
|
256
256
|
- `publish_ack` / `publish_failed` - one per kind:0 profile event and per kind:31990 capability card. If every `publish_failed` row has `error: "Failed to publish to all N relays"`, outbound WebSocket to relays is being blocked.
|
|
257
257
|
- `pool_reset` with `reason: probe_failed` or `self_ping_failed` - the watchdog rebuilt the relay pool; sustained resets mean connectivity is unstable.
|
|
258
|
-
- `heartbeat_failed` - periodic republish calls are failing.
|
|
259
258
|
|
|
260
259
|
Optional deeper network diagnostics (DNS + TCP probe per relay host) are available via `ELISYM_NET_DIAG=1` (see `elisym start --help`).
|
|
261
260
|
|
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, 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';
|
|
4
|
+
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, DEFAULTS, USDC_SOLANA_DEVNET, makeCensor, DEFAULT_REDACT_PATHS, toDTag, 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';
|
|
@@ -83,6 +83,11 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
83
83
|
const target = pickTarget(options);
|
|
84
84
|
const sameLocation = target === "home" ? resolveInHome(agentName) : resolveInProject(agentName, cwd);
|
|
85
85
|
if (sameLocation) {
|
|
86
|
+
if (options.yes) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Agent "${agentName}" already exists at ${sameLocation}. Refusing to overwrite secrets under --yes. Remove the directory first or choose a different name.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
86
91
|
const { overwrite } = await inquirer.prompt([
|
|
87
92
|
{
|
|
88
93
|
type: "confirm",
|
|
@@ -96,30 +101,34 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
96
101
|
return;
|
|
97
102
|
}
|
|
98
103
|
} else if (target === "project" && resolveInHome(agentName)) {
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
if (!options.yes) {
|
|
105
|
+
const { shadow } = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: "confirm",
|
|
108
|
+
name: "shadow",
|
|
109
|
+
message: `A global agent "${agentName}" exists in ~/.elisym/${agentName}/. Create a project-local shadow?`,
|
|
110
|
+
default: true
|
|
111
|
+
}
|
|
112
|
+
]);
|
|
113
|
+
if (!shadow) {
|
|
114
|
+
console.log("Aborted.");
|
|
115
|
+
return;
|
|
105
116
|
}
|
|
106
|
-
]);
|
|
107
|
-
if (!shadow) {
|
|
108
|
-
console.log("Aborted.");
|
|
109
|
-
return;
|
|
110
117
|
}
|
|
111
118
|
} else if (target === "home" && resolveInProject(agentName, cwd)) {
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
if (!options.yes) {
|
|
120
|
+
const { proceed } = await inquirer.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: "confirm",
|
|
123
|
+
name: "proceed",
|
|
124
|
+
message: `A project-local agent "${agentName}" exists. Create a global agent with the same name?`,
|
|
125
|
+
default: true
|
|
126
|
+
}
|
|
127
|
+
]);
|
|
128
|
+
if (!proceed) {
|
|
129
|
+
console.log("Aborted.");
|
|
130
|
+
return;
|
|
118
131
|
}
|
|
119
|
-
]);
|
|
120
|
-
if (!proceed) {
|
|
121
|
-
console.log("Aborted.");
|
|
122
|
-
return;
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
134
|
let yaml;
|
|
@@ -153,24 +162,33 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
153
162
|
llmApiKey = apiKey || void 0;
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (passphrase) {
|
|
165
|
-
const { confirmPassphrase } = await inquirer.prompt([
|
|
165
|
+
let passphrase = "";
|
|
166
|
+
const envPassphrase = process.env.ELISYM_PASSPHRASE;
|
|
167
|
+
if (options.passphrase !== void 0) {
|
|
168
|
+
passphrase = options.passphrase;
|
|
169
|
+
} else if (envPassphrase !== void 0) {
|
|
170
|
+
passphrase = envPassphrase;
|
|
171
|
+
} else {
|
|
172
|
+
const answer = await inquirer.prompt([
|
|
166
173
|
{
|
|
167
174
|
type: "password",
|
|
168
|
-
name: "
|
|
169
|
-
message: "
|
|
170
|
-
mask: "*"
|
|
171
|
-
validate: (value) => value === passphrase || "Passphrases do not match"
|
|
175
|
+
name: "passphrase",
|
|
176
|
+
message: "Passphrase to encrypt secrets (leave empty to skip):",
|
|
177
|
+
mask: "*"
|
|
172
178
|
}
|
|
173
179
|
]);
|
|
180
|
+
passphrase = answer.passphrase ?? "";
|
|
181
|
+
if (passphrase) {
|
|
182
|
+
const { confirmPassphrase } = await inquirer.prompt([
|
|
183
|
+
{
|
|
184
|
+
type: "password",
|
|
185
|
+
name: "confirmPassphrase",
|
|
186
|
+
message: "Confirm passphrase:",
|
|
187
|
+
mask: "*",
|
|
188
|
+
validate: (value) => value === passphrase || "Passphrases do not match"
|
|
189
|
+
}
|
|
190
|
+
]);
|
|
191
|
+
}
|
|
174
192
|
}
|
|
175
193
|
const nostrSecretBytes = generateSecretKey();
|
|
176
194
|
const nostrPubkey = getPublicKey(nostrSecretBytes);
|
|
@@ -599,7 +617,6 @@ async function probeRelays(relays, logger, timeoutMs = TCP_PROBE_TIMEOUT_MS) {
|
|
|
599
617
|
}
|
|
600
618
|
return results;
|
|
601
619
|
}
|
|
602
|
-
var HEARTBEAT_INTERVAL_MS = 10 * 60 * 1e3;
|
|
603
620
|
var MAX_CONCURRENT_JOBS = 10;
|
|
604
621
|
var RECOVERY_MAX_RETRIES = 5;
|
|
605
622
|
var RECOVERY_INTERVAL_SECS = 60;
|
|
@@ -1604,8 +1621,6 @@ var AgentRuntime = class {
|
|
|
1604
1621
|
}
|
|
1605
1622
|
}
|
|
1606
1623
|
};
|
|
1607
|
-
|
|
1608
|
-
// src/skill/index.ts
|
|
1609
1624
|
var SkillRegistry = class {
|
|
1610
1625
|
skills = [];
|
|
1611
1626
|
defaultIndex = null;
|
|
@@ -1617,7 +1632,7 @@ var SkillRegistry = class {
|
|
|
1617
1632
|
}
|
|
1618
1633
|
route(tags) {
|
|
1619
1634
|
for (const tag of tags) {
|
|
1620
|
-
const byName = this.skills.find((skill) => skill.name === tag);
|
|
1635
|
+
const byName = this.skills.find((skill) => toDTag(skill.name) === tag);
|
|
1621
1636
|
if (byName) {
|
|
1622
1637
|
return byName;
|
|
1623
1638
|
}
|
|
@@ -1652,6 +1667,7 @@ var ScriptSkill = class {
|
|
|
1652
1667
|
asset;
|
|
1653
1668
|
image;
|
|
1654
1669
|
imageFile;
|
|
1670
|
+
dir;
|
|
1655
1671
|
inner;
|
|
1656
1672
|
constructor(name, description, capabilities, priceSubunits, assetOrImage, imageOrImageFile, imageFileOrDir, dirOrPrompt, promptOrTools, toolsOrRounds, rounds) {
|
|
1657
1673
|
let asset;
|
|
@@ -1685,6 +1701,7 @@ var ScriptSkill = class {
|
|
|
1685
1701
|
this.asset = asset;
|
|
1686
1702
|
this.image = image;
|
|
1687
1703
|
this.imageFile = imageFile;
|
|
1704
|
+
this.dir = skillDir;
|
|
1688
1705
|
this.inner = new ScriptSkill$1({
|
|
1689
1706
|
name,
|
|
1690
1707
|
description,
|
|
@@ -2090,11 +2107,12 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2090
2107
|
(updated) => mediaCacheDirty = mediaCacheDirty || updated
|
|
2091
2108
|
);
|
|
2092
2109
|
for (const skill of allSkills) {
|
|
2093
|
-
if (skill.image || !skill.imageFile) {
|
|
2110
|
+
if (skill.image || !skill.imageFile || !skill.dir) {
|
|
2094
2111
|
continue;
|
|
2095
2112
|
}
|
|
2096
|
-
const skillRoot =
|
|
2097
|
-
const
|
|
2113
|
+
const skillRoot = skill.dir;
|
|
2114
|
+
const folderName = basename(skillRoot);
|
|
2115
|
+
const cacheKey = `./skills/${folderName}/${skill.imageFile}`;
|
|
2098
2116
|
const absPath = join(skillRoot, skill.imageFile);
|
|
2099
2117
|
const url = await uploadOrReuse(
|
|
2100
2118
|
cacheKey,
|
|
@@ -2196,19 +2214,6 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2196
2214
|
client.ping.sendPong(identity, senderPubkey, nonce).catch(() => {
|
|
2197
2215
|
});
|
|
2198
2216
|
};
|
|
2199
|
-
let heartbeatTimer = null;
|
|
2200
|
-
if (allSkills.length > 0) {
|
|
2201
|
-
const heartbeatCard = buildCard(allSkills[0]);
|
|
2202
|
-
heartbeatTimer = setInterval(async () => {
|
|
2203
|
-
try {
|
|
2204
|
-
await client.discovery.publishCapability(identity, heartbeatCard, kinds);
|
|
2205
|
-
logger.debug({ event: "heartbeat_ack", skill: heartbeatCard.name }, "heartbeat ok");
|
|
2206
|
-
} catch (error) {
|
|
2207
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2208
|
-
logger.warn({ event: "heartbeat_failed", error: message }, "heartbeat publish failed");
|
|
2209
|
-
}
|
|
2210
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
2211
|
-
}
|
|
2212
2217
|
const transport = new NostrTransport(client, identity, [DEFAULT_KIND_OFFSET]);
|
|
2213
2218
|
const ledger = new JobLedger(paths.jobs);
|
|
2214
2219
|
const runtimeConfig = {
|
|
@@ -2264,9 +2269,6 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2264
2269
|
onLog: diagLog,
|
|
2265
2270
|
onStop: () => {
|
|
2266
2271
|
watchdog.stop();
|
|
2267
|
-
if (heartbeatTimer) {
|
|
2268
|
-
clearInterval(heartbeatTimer);
|
|
2269
|
-
}
|
|
2270
2272
|
}
|
|
2271
2273
|
});
|
|
2272
2274
|
console.log(" * Running. Press Ctrl+C to stop.\n");
|
|
@@ -2488,7 +2490,13 @@ function safe(fn) {
|
|
|
2488
2490
|
};
|
|
2489
2491
|
}
|
|
2490
2492
|
var program = new Command().name("elisym").description("CLI agent runner for the elisym network").version(PACKAGE_VERSION);
|
|
2491
|
-
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>/)").
|
|
2493
|
+
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>/)").option(
|
|
2494
|
+
"--passphrase <value>",
|
|
2495
|
+
'Passphrase to encrypt secrets at rest. Empty string ("") skips encryption. Also reads ELISYM_PASSPHRASE env var. When neither is provided, prompts interactively.'
|
|
2496
|
+
).option(
|
|
2497
|
+
"--yes",
|
|
2498
|
+
"Skip confirmation prompts (shadow/sibling-location). Fails closed on an existing agent at the same location - never overwrites secrets silently."
|
|
2499
|
+
).action(
|
|
2492
2500
|
safe(async (name, options) => {
|
|
2493
2501
|
await cmdInit(name, options);
|
|
2494
2502
|
})
|