@elisym/cli 0.7.9 → 0.8.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 +74 -12
- package/dist/index.js +233 -57
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills-examples/README.md +32 -14
- package/skills-examples/static-now/SKILL.md +10 -0
- package/skills-examples/static-now/scripts/now.sh +3 -0
- package/skills-examples/static-welcome/SKILL.md +10 -0
- package/skills-examples/static-welcome/welcome.md +17 -0
- package/skills-examples/uppercase-proxy/SKILL.md +10 -0
- package/skills-examples/uppercase-proxy/scripts/upper.sh +4 -0
package/README.md
CHANGED
|
@@ -152,16 +152,23 @@ then return a concise overview and key points.
|
|
|
152
152
|
|
|
153
153
|
### Frontmatter fields
|
|
154
154
|
|
|
155
|
-
| Field
|
|
156
|
-
|
|
|
157
|
-
| `name`
|
|
158
|
-
| `description`
|
|
159
|
-
| `capabilities`
|
|
160
|
-
| `price`
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
155
|
+
| Field | Required | Type | Description |
|
|
156
|
+
| ------------------- | ------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
157
|
+
| `name` | yes | string | Unique skill identifier. Shown in the marketplace and used internally. |
|
|
158
|
+
| `description` | yes | string | Short one-line description. Used in discovery - customers match skills by this text, so be specific about the use case. |
|
|
159
|
+
| `capabilities` | yes | string[] | Non-empty list of capability tags for NIP-89 discovery. Customers filter agents by these tags. |
|
|
160
|
+
| `price` | no | number | Price per job, denominated in `token` (e.g. `0.01` for 0.01 SOL or 0.01 USDC). Converted to subunits internally. Omit or set `0` for a free skill. |
|
|
161
|
+
| `token` | no | string | Payment asset on Solana: `sol` (default) or `usdc`. Buyer pays in this asset; the agent's wallet receives it. |
|
|
162
|
+
| `mint` | no | string | Override the SPL mint address (base58). Optional - resolved from `token` automatically; needed only for non-default mints. |
|
|
163
|
+
| `image` | no | string | Hero image URL. Shown in the marketplace card. Takes priority over `image_file`. |
|
|
164
|
+
| `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. |
|
|
165
|
+
| `mode` | no | string | Execution mode: `llm` (default), `static-file`, `static-script`, or `dynamic-script`. See [Skill modes](#skill-modes). |
|
|
166
|
+
| `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. |
|
|
167
|
+
| `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. |
|
|
168
|
+
| `script_args` | no | string[] | Extra positional args appended after the script. Script modes only. |
|
|
169
|
+
| `script_timeout_ms` | no | number | Hard timeout for the script in ms (positive integer). Default: `60000`. Script modes only. |
|
|
170
|
+
| `tools` | no | object[] | External scripts the LLM can call via tool-use. `mode: llm` only. |
|
|
171
|
+
| `max_tool_rounds` | no | number | Max LLM-tool interaction rounds per job. Default: `10`. `mode: llm` only. |
|
|
165
172
|
|
|
166
173
|
### Tool definition
|
|
167
174
|
|
|
@@ -184,7 +191,57 @@ Each parameter has:
|
|
|
184
191
|
|
|
185
192
|
### Body (system prompt)
|
|
186
193
|
|
|
187
|
-
Everything after the closing `---` becomes the LLM system prompt. Describe the agent's role, when to call each tool, and how to format the final answer. Keep it concrete - this is what drives the model's behavior for every job.
|
|
194
|
+
Everything after the closing `---` becomes the LLM system prompt. Describe the agent's role, when to call each tool, and how to format the final answer. Keep it concrete - this is what drives the model's behavior for every job. The body is ignored when `mode` is anything other than `llm`.
|
|
195
|
+
|
|
196
|
+
### Skill modes
|
|
197
|
+
|
|
198
|
+
`mode` controls how the runtime turns a paid job into a result. Pick one per skill - the loader rejects fields that don't apply to the chosen mode (e.g. `tools` outside `mode: llm`, `script` outside script modes).
|
|
199
|
+
|
|
200
|
+
| Mode | What it does | Buyer UX | Required field |
|
|
201
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | -------------- |
|
|
202
|
+
| `llm` (default) | Feeds the buyer's input through Anthropic / OpenAI with the body as the system prompt. Optional `tools` for tool-use scripts. | Input box on the buyer side. | - |
|
|
203
|
+
| `static-file` | Returns the contents of a fixed file. Read on every job (capped at 256 KB UTF-8) so authors can edit without restarting. | Buy-only, no input box (capability card publishes `static: true`). | `output_file` |
|
|
204
|
+
| `static-script` | Spawns a script with no stdin and returns its trimmed stdout. Hard timeout via `script_timeout_ms` (default 60s). | Buy-only, no input box (capability card publishes `static: true`). | `script` |
|
|
205
|
+
| `dynamic-script` | Spawns a script and pipes the buyer's text into stdin. Returns trimmed stdout. The skeleton for any crypto-paid HTTP/AI proxy. | Input box on the buyer side; the agent itself is not an LLM. | `script` |
|
|
206
|
+
|
|
207
|
+
When **every** loaded skill is non-LLM, `npx @elisym/cli start` skips the LLM-key check - a fully non-AI provider can run with no `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` configured.
|
|
208
|
+
|
|
209
|
+
Minimal non-LLM examples (drop `mode` to fall back to `llm`):
|
|
210
|
+
|
|
211
|
+
```markdown
|
|
212
|
+
---
|
|
213
|
+
name: welcome-pack
|
|
214
|
+
description: One-page onboarding doc.
|
|
215
|
+
capabilities: [welcome-pack]
|
|
216
|
+
price: 0.001
|
|
217
|
+
mode: static-file
|
|
218
|
+
output_file: ./welcome.md
|
|
219
|
+
---
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```markdown
|
|
223
|
+
---
|
|
224
|
+
name: utc-now
|
|
225
|
+
description: Current UTC timestamp.
|
|
226
|
+
capabilities: [utc-now]
|
|
227
|
+
price: 0.0005
|
|
228
|
+
mode: static-script
|
|
229
|
+
script: ./scripts/now.sh
|
|
230
|
+
---
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```markdown
|
|
234
|
+
---
|
|
235
|
+
name: uppercase
|
|
236
|
+
description: Uppercase any text. Skeleton for a paid model proxy.
|
|
237
|
+
capabilities: [uppercase]
|
|
238
|
+
price: 0.0005
|
|
239
|
+
mode: dynamic-script
|
|
240
|
+
script: ./scripts/upper.sh
|
|
241
|
+
---
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`output_file` and `script` are resolved relative to the skill directory and rejected if they escape it (`..` traversal, absolute paths outside the skill dir).
|
|
188
245
|
|
|
189
246
|
### How `command` is executed
|
|
190
247
|
|
|
@@ -240,7 +297,12 @@ If you cannot make a side effect idempotent, document the risk clearly in the sk
|
|
|
240
297
|
|
|
241
298
|
### More examples
|
|
242
299
|
|
|
243
|
-
See `skills-examples/` for working skills:
|
|
300
|
+
See `skills-examples/` for working skills:
|
|
301
|
+
|
|
302
|
+
- **LLM:** `general-assistant` (free), `usdc-summarize`, `site-status`, `whois-lookup`, `github-repo`, `stock-price`, `trending`, `youtube-summary` (multi-round tool-use).
|
|
303
|
+
- **Non-LLM:** `static-welcome` (`mode: static-file`), `static-now` (`mode: static-script`), `uppercase-proxy` (`mode: dynamic-script`).
|
|
304
|
+
|
|
305
|
+
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.
|
|
244
306
|
|
|
245
307
|
## Troubleshooting
|
|
246
308
|
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { lookup } from 'node:dns/promises';
|
|
|
13
13
|
import { Socket } from 'node:net';
|
|
14
14
|
import pino from 'pino';
|
|
15
15
|
import pLimit from 'p-limit';
|
|
16
|
-
import { parseSkillMd, validateSkillFrontmatter, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
16
|
+
import { parseSkillMd, validateSkillFrontmatter, resolveInsidePath, DEFAULT_SCRIPT_TIMEOUT_MS, StaticScriptSkill as StaticScriptSkill$1, DynamicScriptSkill as DynamicScriptSkill$1, StaticFileSkill as StaticFileSkill$1, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
17
17
|
import { fileURLToPath } from 'node:url';
|
|
18
18
|
|
|
19
19
|
var __defProp = Object.defineProperty;
|
|
@@ -363,7 +363,7 @@ async function cmdProfile(name) {
|
|
|
363
363
|
if (!name) {
|
|
364
364
|
const agents = await listAgents(cwd);
|
|
365
365
|
if (agents.length === 0) {
|
|
366
|
-
console.error("No agents found. Run `elisym init` first.");
|
|
366
|
+
console.error("No agents found. Run `npx @elisym/cli init` first.");
|
|
367
367
|
process.exit(1);
|
|
368
368
|
}
|
|
369
369
|
const { selected } = await inquirer.prompt([
|
|
@@ -1660,12 +1660,122 @@ var SkillRegistry = class {
|
|
|
1660
1660
|
return this.skills;
|
|
1661
1661
|
}
|
|
1662
1662
|
};
|
|
1663
|
+
var StaticFileSkill = class {
|
|
1664
|
+
name;
|
|
1665
|
+
description;
|
|
1666
|
+
capabilities;
|
|
1667
|
+
priceSubunits;
|
|
1668
|
+
asset;
|
|
1669
|
+
mode = "static-file";
|
|
1670
|
+
image;
|
|
1671
|
+
imageFile;
|
|
1672
|
+
dir;
|
|
1673
|
+
inner;
|
|
1674
|
+
constructor(params) {
|
|
1675
|
+
this.name = params.name;
|
|
1676
|
+
this.description = params.description;
|
|
1677
|
+
this.capabilities = params.capabilities;
|
|
1678
|
+
this.priceSubunits = params.priceSubunits;
|
|
1679
|
+
this.asset = params.asset;
|
|
1680
|
+
this.image = params.image;
|
|
1681
|
+
this.imageFile = params.imageFile;
|
|
1682
|
+
this.dir = params.dir;
|
|
1683
|
+
this.inner = new StaticFileSkill$1({
|
|
1684
|
+
name: params.name,
|
|
1685
|
+
description: params.description,
|
|
1686
|
+
capabilities: params.capabilities,
|
|
1687
|
+
priceSubunits: BigInt(Math.round(params.priceSubunits)),
|
|
1688
|
+
asset: params.asset,
|
|
1689
|
+
outputFilePath: params.outputFilePath,
|
|
1690
|
+
image: params.image,
|
|
1691
|
+
imageFile: params.imageFile
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
async execute(input, ctx) {
|
|
1695
|
+
return this.inner.execute(input, ctx);
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
var StaticScriptSkill = class {
|
|
1699
|
+
name;
|
|
1700
|
+
description;
|
|
1701
|
+
capabilities;
|
|
1702
|
+
priceSubunits;
|
|
1703
|
+
asset;
|
|
1704
|
+
mode = "static-script";
|
|
1705
|
+
image;
|
|
1706
|
+
imageFile;
|
|
1707
|
+
dir;
|
|
1708
|
+
inner;
|
|
1709
|
+
constructor(params) {
|
|
1710
|
+
this.name = params.name;
|
|
1711
|
+
this.description = params.description;
|
|
1712
|
+
this.capabilities = params.capabilities;
|
|
1713
|
+
this.priceSubunits = params.priceSubunits;
|
|
1714
|
+
this.asset = params.asset;
|
|
1715
|
+
this.image = params.image;
|
|
1716
|
+
this.imageFile = params.imageFile;
|
|
1717
|
+
this.dir = params.dir;
|
|
1718
|
+
this.inner = new StaticScriptSkill$1({
|
|
1719
|
+
name: params.name,
|
|
1720
|
+
description: params.description,
|
|
1721
|
+
capabilities: params.capabilities,
|
|
1722
|
+
priceSubunits: BigInt(Math.round(params.priceSubunits)),
|
|
1723
|
+
asset: params.asset,
|
|
1724
|
+
scriptPath: params.scriptPath,
|
|
1725
|
+
scriptArgs: params.scriptArgs,
|
|
1726
|
+
scriptTimeoutMs: params.scriptTimeoutMs,
|
|
1727
|
+
image: params.image,
|
|
1728
|
+
imageFile: params.imageFile
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
async execute(input, ctx) {
|
|
1732
|
+
return this.inner.execute(input, ctx);
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
var DynamicScriptSkill = class {
|
|
1736
|
+
name;
|
|
1737
|
+
description;
|
|
1738
|
+
capabilities;
|
|
1739
|
+
priceSubunits;
|
|
1740
|
+
asset;
|
|
1741
|
+
mode = "dynamic-script";
|
|
1742
|
+
image;
|
|
1743
|
+
imageFile;
|
|
1744
|
+
dir;
|
|
1745
|
+
inner;
|
|
1746
|
+
constructor(params) {
|
|
1747
|
+
this.name = params.name;
|
|
1748
|
+
this.description = params.description;
|
|
1749
|
+
this.capabilities = params.capabilities;
|
|
1750
|
+
this.priceSubunits = params.priceSubunits;
|
|
1751
|
+
this.asset = params.asset;
|
|
1752
|
+
this.image = params.image;
|
|
1753
|
+
this.imageFile = params.imageFile;
|
|
1754
|
+
this.dir = params.dir;
|
|
1755
|
+
this.inner = new DynamicScriptSkill$1({
|
|
1756
|
+
name: params.name,
|
|
1757
|
+
description: params.description,
|
|
1758
|
+
capabilities: params.capabilities,
|
|
1759
|
+
priceSubunits: BigInt(Math.round(params.priceSubunits)),
|
|
1760
|
+
asset: params.asset,
|
|
1761
|
+
scriptPath: params.scriptPath,
|
|
1762
|
+
scriptArgs: params.scriptArgs,
|
|
1763
|
+
scriptTimeoutMs: params.scriptTimeoutMs,
|
|
1764
|
+
image: params.image,
|
|
1765
|
+
imageFile: params.imageFile
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
async execute(input, ctx) {
|
|
1769
|
+
return this.inner.execute(input, ctx);
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1663
1772
|
var ScriptSkill = class {
|
|
1664
1773
|
name;
|
|
1665
1774
|
description;
|
|
1666
1775
|
capabilities;
|
|
1667
1776
|
priceSubunits;
|
|
1668
1777
|
asset;
|
|
1778
|
+
mode = "llm";
|
|
1669
1779
|
image;
|
|
1670
1780
|
imageFile;
|
|
1671
1781
|
dir;
|
|
@@ -1723,6 +1833,74 @@ var ScriptSkill = class {
|
|
|
1723
1833
|
};
|
|
1724
1834
|
|
|
1725
1835
|
// src/skill/loader.ts
|
|
1836
|
+
function buildCliSkill(parsed, entryPath) {
|
|
1837
|
+
switch (parsed.mode) {
|
|
1838
|
+
case "llm":
|
|
1839
|
+
return new ScriptSkill(
|
|
1840
|
+
parsed.name,
|
|
1841
|
+
parsed.description,
|
|
1842
|
+
parsed.capabilities,
|
|
1843
|
+
Number(parsed.priceSubunits),
|
|
1844
|
+
parsed.asset,
|
|
1845
|
+
parsed.image,
|
|
1846
|
+
parsed.imageFile,
|
|
1847
|
+
entryPath,
|
|
1848
|
+
parsed.systemPrompt,
|
|
1849
|
+
parsed.tools,
|
|
1850
|
+
parsed.maxToolRounds
|
|
1851
|
+
);
|
|
1852
|
+
case "static-file": {
|
|
1853
|
+
if (parsed.outputFile === void 0) {
|
|
1854
|
+
throw new Error(
|
|
1855
|
+
`SKILL.md "${parsed.name}": internal error - outputFile missing for mode 'static-file'`
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
const outputFilePath = resolveInsidePath(entryPath, parsed.outputFile);
|
|
1859
|
+
if (!outputFilePath) {
|
|
1860
|
+
throw new Error(
|
|
1861
|
+
`SKILL.md "${parsed.name}": "output_file" must stay inside the skill directory`
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
return new StaticFileSkill({
|
|
1865
|
+
name: parsed.name,
|
|
1866
|
+
description: parsed.description,
|
|
1867
|
+
capabilities: parsed.capabilities,
|
|
1868
|
+
priceSubunits: Number(parsed.priceSubunits),
|
|
1869
|
+
asset: parsed.asset,
|
|
1870
|
+
outputFilePath,
|
|
1871
|
+
image: parsed.image,
|
|
1872
|
+
imageFile: parsed.imageFile,
|
|
1873
|
+
dir: entryPath
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
case "static-script":
|
|
1877
|
+
case "dynamic-script": {
|
|
1878
|
+
if (parsed.script === void 0) {
|
|
1879
|
+
throw new Error(
|
|
1880
|
+
`SKILL.md "${parsed.name}": internal error - script missing for mode '${parsed.mode}'`
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
const scriptPath = resolveInsidePath(entryPath, parsed.script);
|
|
1884
|
+
if (!scriptPath) {
|
|
1885
|
+
throw new Error(`SKILL.md "${parsed.name}": "script" must stay inside the skill directory`);
|
|
1886
|
+
}
|
|
1887
|
+
const Ctor = parsed.mode === "static-script" ? StaticScriptSkill : DynamicScriptSkill;
|
|
1888
|
+
return new Ctor({
|
|
1889
|
+
name: parsed.name,
|
|
1890
|
+
description: parsed.description,
|
|
1891
|
+
capabilities: parsed.capabilities,
|
|
1892
|
+
priceSubunits: Number(parsed.priceSubunits),
|
|
1893
|
+
asset: parsed.asset,
|
|
1894
|
+
scriptPath,
|
|
1895
|
+
scriptArgs: parsed.scriptArgs,
|
|
1896
|
+
scriptTimeoutMs: parsed.scriptTimeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS,
|
|
1897
|
+
image: parsed.image,
|
|
1898
|
+
imageFile: parsed.imageFile,
|
|
1899
|
+
dir: entryPath
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1726
1904
|
function loadSkillsFromDir(skillsDir) {
|
|
1727
1905
|
const skills = [];
|
|
1728
1906
|
let entries;
|
|
@@ -1747,21 +1925,7 @@ function loadSkillsFromDir(skillsDir) {
|
|
|
1747
1925
|
const parsed = validateSkillFrontmatter(frontmatter, systemPrompt, {
|
|
1748
1926
|
allowFreeSkills: true
|
|
1749
1927
|
});
|
|
1750
|
-
skills.push(
|
|
1751
|
-
new ScriptSkill(
|
|
1752
|
-
parsed.name,
|
|
1753
|
-
parsed.description,
|
|
1754
|
-
parsed.capabilities,
|
|
1755
|
-
Number(parsed.priceSubunits),
|
|
1756
|
-
parsed.asset,
|
|
1757
|
-
parsed.image,
|
|
1758
|
-
parsed.imageFile,
|
|
1759
|
-
entryPath,
|
|
1760
|
-
parsed.systemPrompt,
|
|
1761
|
-
parsed.tools,
|
|
1762
|
-
parsed.maxToolRounds
|
|
1763
|
-
)
|
|
1764
|
-
);
|
|
1928
|
+
skills.push(buildCliSkill(parsed, entryPath));
|
|
1765
1929
|
} catch (e) {
|
|
1766
1930
|
const message = e instanceof Error ? e.message : String(e);
|
|
1767
1931
|
console.warn(` ! Skipping skill "${entry}": ${message}`);
|
|
@@ -2037,36 +2201,6 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2037
2201
|
`);
|
|
2038
2202
|
}
|
|
2039
2203
|
}
|
|
2040
|
-
if (!loaded.yaml.llm) {
|
|
2041
|
-
console.error(" ! No LLM configured. Run `elisym init` to set up LLM.\n");
|
|
2042
|
-
process.exit(1);
|
|
2043
|
-
}
|
|
2044
|
-
if (!loaded.secrets.llm_api_key) {
|
|
2045
|
-
console.error(
|
|
2046
|
-
` ! No LLM API key. Set ${loaded.yaml.llm.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"} env var or update the agent's secrets.
|
|
2047
|
-
`
|
|
2048
|
-
);
|
|
2049
|
-
process.exit(1);
|
|
2050
|
-
}
|
|
2051
|
-
const llmProvider = loaded.yaml.llm.provider;
|
|
2052
|
-
const keyEnvVar = llmProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
2053
|
-
process.stdout.write(` Verifying ${llmProvider} API key... `);
|
|
2054
|
-
const verification = await verifyLlmApiKey(llmProvider, loaded.secrets.llm_api_key);
|
|
2055
|
-
if (verification.ok) {
|
|
2056
|
-
console.log("ok");
|
|
2057
|
-
} else if (verification.reason === "invalid") {
|
|
2058
|
-
console.log("INVALID");
|
|
2059
|
-
console.error(` ! ${llmProvider} rejected the API key (HTTP ${verification.status}).`);
|
|
2060
|
-
console.error(` Update it via \`elisym profile\` or set ${keyEnvVar} to a valid key.
|
|
2061
|
-
`);
|
|
2062
|
-
process.exit(1);
|
|
2063
|
-
} else {
|
|
2064
|
-
console.log("unavailable");
|
|
2065
|
-
console.warn(
|
|
2066
|
-
` ! Could not verify ${llmProvider} API key (${verification.error}). Continuing - jobs will fail if the key is invalid.
|
|
2067
|
-
`
|
|
2068
|
-
);
|
|
2069
|
-
}
|
|
2070
2204
|
const paths = agentPaths(loaded.dir);
|
|
2071
2205
|
const skillsDir = paths.skills;
|
|
2072
2206
|
const allSkills = loadSkillsFromDir(skillsDir);
|
|
@@ -2087,16 +2221,56 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2087
2221
|
console.log();
|
|
2088
2222
|
const hasPaid = allSkills.some((s) => s.priceSubunits > 0);
|
|
2089
2223
|
if (hasPaid && !solanaAddress) {
|
|
2090
|
-
console.error(
|
|
2224
|
+
console.error(
|
|
2225
|
+
" ! Paid skills require a Solana address. Run `npx @elisym/cli init` to configure.\n"
|
|
2226
|
+
);
|
|
2091
2227
|
process.exit(1);
|
|
2092
2228
|
}
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2229
|
+
const hasLlmSkill = allSkills.some((skill) => skill.mode === "llm");
|
|
2230
|
+
let llm;
|
|
2231
|
+
if (hasLlmSkill) {
|
|
2232
|
+
if (!loaded.yaml.llm) {
|
|
2233
|
+
console.error(" ! No LLM configured. Run `npx @elisym/cli init` to set up LLM.\n");
|
|
2234
|
+
process.exit(1);
|
|
2235
|
+
}
|
|
2236
|
+
if (!loaded.secrets.llm_api_key) {
|
|
2237
|
+
console.error(
|
|
2238
|
+
` ! No LLM API key. Set ${loaded.yaml.llm.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"} env var or update the agent's secrets.
|
|
2239
|
+
`
|
|
2240
|
+
);
|
|
2241
|
+
process.exit(1);
|
|
2242
|
+
}
|
|
2243
|
+
const llmProvider = loaded.yaml.llm.provider;
|
|
2244
|
+
const keyEnvVar = llmProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
2245
|
+
process.stdout.write(` Verifying ${llmProvider} API key... `);
|
|
2246
|
+
const verification = await verifyLlmApiKey(llmProvider, loaded.secrets.llm_api_key);
|
|
2247
|
+
if (verification.ok) {
|
|
2248
|
+
console.log("ok");
|
|
2249
|
+
} else if (verification.reason === "invalid") {
|
|
2250
|
+
console.log("INVALID");
|
|
2251
|
+
console.error(` ! ${llmProvider} rejected the API key (HTTP ${verification.status}).`);
|
|
2252
|
+
console.error(
|
|
2253
|
+
` Update it via \`npx @elisym/cli profile\` or set ${keyEnvVar} to a valid key.
|
|
2254
|
+
`
|
|
2255
|
+
);
|
|
2256
|
+
process.exit(1);
|
|
2257
|
+
} else {
|
|
2258
|
+
console.log("unavailable");
|
|
2259
|
+
console.warn(
|
|
2260
|
+
` ! Could not verify ${llmProvider} API key (${verification.error}). Continuing - jobs will fail if the key is invalid.
|
|
2261
|
+
`
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
llm = createLlmClient({
|
|
2265
|
+
provider: loaded.yaml.llm.provider,
|
|
2266
|
+
apiKey: loaded.secrets.llm_api_key,
|
|
2267
|
+
model: loaded.yaml.llm.model,
|
|
2268
|
+
maxTokens: loaded.yaml.llm.max_tokens,
|
|
2269
|
+
logUsage: true
|
|
2270
|
+
});
|
|
2271
|
+
} else {
|
|
2272
|
+
console.log(" No LLM skills loaded; skipping LLM key check.\n");
|
|
2273
|
+
}
|
|
2100
2274
|
const skillCtx = {
|
|
2101
2275
|
llm,
|
|
2102
2276
|
agentName,
|
|
@@ -2177,11 +2351,13 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2177
2351
|
}
|
|
2178
2352
|
const kinds = [jobRequestKind(DEFAULT_KIND_OFFSET)];
|
|
2179
2353
|
function buildCard(skill) {
|
|
2354
|
+
const isStatic = skill.mode === "static-file" || skill.mode === "static-script";
|
|
2180
2355
|
return {
|
|
2181
2356
|
name: skill.name,
|
|
2182
2357
|
description: skill.description,
|
|
2183
2358
|
capabilities: skill.capabilities,
|
|
2184
2359
|
image: skill.image,
|
|
2360
|
+
...isStatic ? { static: true } : {},
|
|
2185
2361
|
payment: solanaAddress ? {
|
|
2186
2362
|
chain: "solana",
|
|
2187
2363
|
network: walletNetwork,
|
|
@@ -2370,7 +2546,7 @@ async function resolveStartAgentName(nameArg, cwd) {
|
|
|
2370
2546
|
}
|
|
2371
2547
|
const agents = await listAgents(cwd);
|
|
2372
2548
|
if (agents.length === 0) {
|
|
2373
|
-
console.log("No agents configured. Run `elisym init` first.");
|
|
2549
|
+
console.log("No agents configured. Run `npx @elisym/cli init` first.");
|
|
2374
2550
|
process.exit(1);
|
|
2375
2551
|
}
|
|
2376
2552
|
const { default: inquirer } = await import('inquirer');
|
|
@@ -2546,7 +2722,7 @@ program.command("list").description("List all agents (project-local and home-glo
|
|
|
2546
2722
|
const cwd = process.cwd();
|
|
2547
2723
|
const agents = await listAgents(cwd);
|
|
2548
2724
|
if (agents.length === 0) {
|
|
2549
|
-
console.log("No agents found. Run `elisym init` to create one.");
|
|
2725
|
+
console.log("No agents found. Run `npx @elisym/cli init` to create one.");
|
|
2550
2726
|
return;
|
|
2551
2727
|
}
|
|
2552
2728
|
console.log("\nAgents:");
|