@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 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, heartbeat, 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 |
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
- const { shadow } = await inquirer.prompt([
100
- {
101
- type: "confirm",
102
- name: "shadow",
103
- message: `A global agent "${agentName}" exists in ~/.elisym/${agentName}/. Create a project-local shadow?`,
104
- default: true
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
- const { proceed } = await inquirer.prompt([
113
- {
114
- type: "confirm",
115
- name: "proceed",
116
- message: `A project-local agent "${agentName}" exists. Create a global agent with the same name?`,
117
- default: true
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
- const { passphrase } = await inquirer.prompt([
157
- {
158
- type: "password",
159
- name: "passphrase",
160
- message: "Passphrase to encrypt secrets (leave empty to skip):",
161
- mask: "*"
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: "confirmPassphrase",
169
- message: "Confirm passphrase:",
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 = join(skillsDir, skill.name);
2097
- const cacheKey = `./skills/${skill.name}/${skill.imageFile}`;
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>/)").action(
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
  })