@elisym/cli 0.15.1 → 0.16.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
@@ -21,7 +21,7 @@ npx @elisym/cli start # Start provider mode
21
21
 
22
22
  ### Docker
23
23
 
24
- Each agent lives in its own directory: `elisym.yaml` (public config), `.secrets.json` (encrypted keys), `.media-cache.json` (uploaded image URLs), `.jobs.json` (ledger), and a `skills/` subfolder. Two locations are supported, and the CLI resolves by walking up from the current working directory:
24
+ Each agent lives in its own directory: `elisym.yaml` (public config), `.secrets.json` (encrypted keys), `.media-cache.json` (uploaded image URLs), `.jobs.json` (ledger), a `skills/` subfolder, and an optional `policies/` subfolder. Two locations are supported, and the CLI resolves by walking up from the current working directory:
25
25
 
26
26
  - **Project-local**: `<project>/.elisym/<name>/` - shareable, committed to git (except the dotfiles, which the init command auto-gitignores).
27
27
  - **Home-global**: `~/.elisym/<name>/` - private, use for ad-hoc or MCP-created agents.
@@ -89,17 +89,17 @@ 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] --defaults` | Non-interactive - skip every prompt and use wizard defaults (description, default relays, no payments, no LLM, no encryption). Mutually exclusive with `--config`; implies `--yes`. |
97
- | `elisym init [name] --local` | Create in project `.elisym/<name>/` (default: `~/.elisym/<name>/`) |
98
- | `elisym start [name]` | Start agent in provider mode |
99
- | `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`. |
100
- | `elisym list` | List all agents (project-local + home-global) |
101
- | `elisym profile [name]` | Edit agent profile, wallet, and LLM settings |
102
- | `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] --defaults` | Non-interactive - skip every prompt and use wizard defaults (description, default relays, no payments, no LLM, no encryption). Mutually exclusive with `--config`; implies `--yes`. |
97
+ | `elisym init [name] --local` | Create in project `.elisym/<name>/` (default: `~/.elisym/<name>/`) |
98
+ | `npx @elisym/cli start [name]` | Start agent in provider mode |
99
+ | `npx @elisym/cli 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`. |
100
+ | `elisym list` | List all agents (project-local + home-global) |
101
+ | `elisym profile [name]` | Edit agent profile, wallet, and LLM settings |
102
+ | `elisym wallet [name]` | Show Solana wallet balance |
103
103
 
104
104
  Skills live inside each agent directory at `<agentDir>/skills/<skill-name>/SKILL.md`:
105
105
 
@@ -116,6 +116,9 @@ my-project/
116
116
  scripts/summarize.py
117
117
  general-assistant/
118
118
  SKILL.md
119
+ policies/ # optional - one *.md per legal/operational policy
120
+ tos.md
121
+ privacy.md
119
122
  .secrets.json # encrypted Nostr/LLM keys (gitignored)
120
123
  .media-cache.json # sha256 -> uploaded URL cache (gitignored)
121
124
  .jobs.json # crash-recovery ledger (gitignored)
@@ -164,7 +167,7 @@ then return a concise overview and key points.
164
167
  | `token` | no | string | Payment asset on Solana: `sol` (default) or `usdc`. Buyer pays in this asset; the agent's wallet receives it. |
165
168
  | `mint` | no | string | Override the SPL mint address (base58). Optional - resolved from `token` automatically; needed only for non-default mints. |
166
169
  | `image` | no | string | Hero image URL. Shown in the marketplace card. Takes priority over `image_file`. |
167
- | `image_file` | no | string | Local file path (relative to the skill directory). Uploaded on `elisym start` and cached by sha256 in `<agentDir>/.media-cache.json`; the SKILL.md itself is not modified. |
170
+ | `image_file` | no | string | Local file path (relative to the skill directory). Uploaded on `npx @elisym/cli start` and cached by sha256 in `<agentDir>/.media-cache.json`; the SKILL.md itself is not modified. |
168
171
  | `mode` | no | string | Execution mode: `llm` (default), `static-file`, `static-script`, or `dynamic-script`. See [Skill modes](#skill-modes). |
169
172
  | `output_file` | required when `mode: static-file` | string | Path (relative to the skill dir) of the file whose contents are returned as the job result. Read on every job, capped at 256 KB. Must stay inside the skill directory. |
170
173
  | `script` | required when `mode: static-script` or `mode: dynamic-script` | string | Path (relative to the skill dir) of the script to spawn. `child_process.spawn` runs it directly - list the interpreter in a shebang or use a binary. Must stay inside the skill directory. |
@@ -316,9 +319,37 @@ See `skills-examples/` for working skills:
316
319
 
317
320
  Most LLM examples are priced in **USDC on Solana devnet** (`token: usdc`); the non-LLM trio is priced in **SOL** for variety. See [`skills-examples/README.md`](./skills-examples/README.md) for the full table and install commands.
318
321
 
322
+ ## Policies
323
+
324
+ Optional. Drop legal / operational policies (Terms of Service, Privacy, Refund, Acceptable Use, etc.) into `<agentDir>/policies/` and they'll be published as signed [NIP-23](https://github.com/nostr-protocol/nips/blob/master/23.md) long-form articles on `npx @elisym/cli start`. Each markdown file becomes one policy event - the filename without `.md` is the policy `type` slug.
325
+
326
+ ```
327
+ <agentDir>/policies/
328
+ tos.md
329
+ privacy.md
330
+ refund.md
331
+ ```
332
+
333
+ Minimal `tos.md`:
334
+
335
+ ```markdown
336
+ ---
337
+ title: Terms of Service
338
+ version: '1.0'
339
+ ---
340
+
341
+ ## Terms
342
+
343
+ By submitting a job to this agent you agree to...
344
+ ```
345
+
346
+ Policies show up in the elisym web app under the **Policies** tab on the agent page, and are readable via the `get_agent_policies` MCP tool.
347
+
348
+ > Full reference for the policies workflow, frontmatter fields, type vocabulary, limits, update / removal flow, and reading paths lives in [`POLICIES.md`](./POLICIES.md).
349
+
319
350
  ## Troubleshooting
320
351
 
321
- 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`:
352
+ If `npx @elisym/cli 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`:
322
353
 
323
354
  ```
324
355
  npx @elisym/cli start <agent-name> --verbose
@@ -330,7 +361,7 @@ The debug firehose on stderr includes:
330
361
  - `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.
331
362
  - `pool_reset` with `reason: probe_failed` or `self_ping_failed` - the watchdog rebuilt the relay pool; sustained resets mean connectivity is unstable.
332
363
 
333
- Optional deeper network diagnostics (DNS + TCP probe per relay host) are available via `ELISYM_NET_DIAG=1` (see `elisym start --help`).
364
+ Optional deeper network diagnostics (DNS + TCP probe per relay host) are available via `ELISYM_NET_DIAG=1` (see `npx @elisym/cli start --help`).
334
365
 
335
366
  ## Commands
336
367
 
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env -S node --no-deprecation
2
- import { readFileSync, readdirSync, statSync, renameSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { readFileSync, existsSync, 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, USDC_SOLANA_DEVNET, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet, KIND_JOB_FEEDBACK, NATIVE_SOL } from '@elisym/sdk';
5
- import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
4
+ import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, ElisymClient, MediaService, POLICY_D_TAG_PREFIX, KIND_LONG_FORM_ARTICLE, POLICY_T_TAG, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet, KIND_JOB_FEEDBACK, NATIVE_SOL } from '@elisym/sdk';
5
+ import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, loadPoliciesFromDir, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
6
6
  import { isAddress, createSolanaRpc, address } from '@solana/kit';
7
7
  import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
8
8
  import YAML from 'yaml';
@@ -3764,6 +3764,57 @@ async function cmdStart(nameArg, options = {}) {
3764
3764
  console.warn(` ! Failed to publish profile: ${e.message}`);
3765
3765
  logger.warn({ event: "publish_failed", kind: 0, error: e.message }, "profile publish failed");
3766
3766
  }
3767
+ const policies = existsSync(paths.policies) ? loadPoliciesFromDir(paths.policies) : [];
3768
+ const localPolicyDTags = new Set(
3769
+ policies.map((policy) => `${POLICY_D_TAG_PREFIX}${policy.type}`)
3770
+ );
3771
+ for (const policy of policies) {
3772
+ try {
3773
+ const { naddr } = await client.policies.publishPolicy(identity, policy);
3774
+ console.log(` * Policy: ${policy.type}@${policy.version} -> ${naddr}`);
3775
+ logger.debug(
3776
+ { event: "publish_ack", kind: KIND_LONG_FORM_ARTICLE, policy: policy.type, naddr },
3777
+ "policy published"
3778
+ );
3779
+ } catch (e) {
3780
+ console.warn(` ! Failed to publish policy "${policy.type}": ${e.message}`);
3781
+ logger.warn(
3782
+ {
3783
+ event: "publish_failed",
3784
+ kind: KIND_LONG_FORM_ARTICLE,
3785
+ policy: policy.type,
3786
+ error: e.message
3787
+ },
3788
+ "policy publish failed"
3789
+ );
3790
+ }
3791
+ }
3792
+ try {
3793
+ const existingPolicies = await client.pool.querySync({
3794
+ kinds: [KIND_LONG_FORM_ARTICLE],
3795
+ authors: [identity.publicKey],
3796
+ "#t": [POLICY_T_TAG]
3797
+ });
3798
+ for (const event of existingPolicies) {
3799
+ const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
3800
+ if (!dTag || localPolicyDTags.has(dTag)) {
3801
+ continue;
3802
+ }
3803
+ if (!event.content) {
3804
+ continue;
3805
+ }
3806
+ const type = event.tags.find((tag) => tag[0] === "policy_type")?.[1];
3807
+ if (!type) {
3808
+ continue;
3809
+ }
3810
+ try {
3811
+ await client.policies.deletePolicy(identity, type);
3812
+ console.log(` Removed stale policy: ${type}`);
3813
+ } catch {
3814
+ }
3815
+ }
3816
+ } catch {
3817
+ }
3767
3818
  const kinds = [jobRequestKind(DEFAULT_KIND_OFFSET)];
3768
3819
  function buildCard(skill) {
3769
3820
  const isStatic = skill.mode === "static-file" || skill.mode === "static-script";