@elisym/cli 0.7.10 → 0.8.1

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
@@ -152,16 +152,23 @@ then return a concise overview and key points.
152
152
 
153
153
  ### Frontmatter fields
154
154
 
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 in SOL (e.g. `0.01`). Converted to lamports internally. Omit or set `0` for a free skill. |
161
- | `image` | no | string | Hero image URL. Shown in the marketplace card. Takes priority over `image_file`. |
162
- | `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. |
163
- | `tools` | no | object[] | External scripts the LLM can call via tool-use. Omit if the skill is pure prompt + LLM. |
164
- | `max_tool_rounds` | no | number | Max LLM-tool interaction rounds per job. Default: `10`. Raise for multi-step flows (e.g. chunked transcripts). |
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: `youtube-summary`, `github-repo`, `stock-price`, `whois-lookup`, `site-status`, `trending`, `general-assistant`.
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;
@@ -297,11 +297,28 @@ async function promptYaml(inquirer) {
297
297
  name: "llmProvider",
298
298
  message: "LLM provider:",
299
299
  choices: [
300
+ {
301
+ name: "None (non-LLM agent - static-file / static-script / dynamic-script only)",
302
+ value: "none"
303
+ },
300
304
  { name: "Anthropic (Claude)", value: "anthropic" },
301
305
  { name: "OpenAI (GPT)", value: "openai" }
302
306
  ]
303
307
  }
304
308
  ]);
309
+ const baseYaml = {
310
+ display_name: displayName || void 0,
311
+ description,
312
+ picture: picture || void 0,
313
+ banner: banner || void 0,
314
+ relays: [...RELAYS],
315
+ payments: solanaAddress ? [{ chain: "solana", network: "devnet", address: solanaAddress }] : [],
316
+ security: {}
317
+ };
318
+ if (llmProvider === "none") {
319
+ const yaml2 = ElisymYamlSchema.parse(baseYaml);
320
+ return { yaml: yaml2 };
321
+ }
305
322
  const envKey = llmProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
306
323
  let apiKey = envKey;
307
324
  if (!apiKey) {
@@ -334,14 +351,8 @@ async function promptYaml(inquirer) {
334
351
  }
335
352
  ]);
336
353
  const yaml = ElisymYamlSchema.parse({
337
- display_name: displayName || void 0,
338
- description,
339
- picture: picture || void 0,
340
- banner: banner || void 0,
341
- relays: [...RELAYS],
342
- payments: solanaAddress ? [{ chain: "solana", network: "devnet", address: solanaAddress }] : [],
343
- llm: { provider: llmProvider, model, max_tokens: maxTokens },
344
- security: {}
354
+ ...baseYaml,
355
+ llm: { provider: llmProvider, model, max_tokens: maxTokens }
345
356
  });
346
357
  return { yaml, apiKey: envKey ? void 0 : apiKey };
347
358
  }
@@ -363,7 +374,7 @@ async function cmdProfile(name) {
363
374
  if (!name) {
364
375
  const agents = await listAgents(cwd);
365
376
  if (agents.length === 0) {
366
- console.error("No agents found. Run `elisym init` first.");
377
+ console.error("No agents found. Run `npx @elisym/cli init` first.");
367
378
  process.exit(1);
368
379
  }
369
380
  const { selected } = await inquirer.prompt([
@@ -1660,12 +1671,122 @@ var SkillRegistry = class {
1660
1671
  return this.skills;
1661
1672
  }
1662
1673
  };
1674
+ var StaticFileSkill = class {
1675
+ name;
1676
+ description;
1677
+ capabilities;
1678
+ priceSubunits;
1679
+ asset;
1680
+ mode = "static-file";
1681
+ image;
1682
+ imageFile;
1683
+ dir;
1684
+ inner;
1685
+ constructor(params) {
1686
+ this.name = params.name;
1687
+ this.description = params.description;
1688
+ this.capabilities = params.capabilities;
1689
+ this.priceSubunits = params.priceSubunits;
1690
+ this.asset = params.asset;
1691
+ this.image = params.image;
1692
+ this.imageFile = params.imageFile;
1693
+ this.dir = params.dir;
1694
+ this.inner = new StaticFileSkill$1({
1695
+ name: params.name,
1696
+ description: params.description,
1697
+ capabilities: params.capabilities,
1698
+ priceSubunits: BigInt(Math.round(params.priceSubunits)),
1699
+ asset: params.asset,
1700
+ outputFilePath: params.outputFilePath,
1701
+ image: params.image,
1702
+ imageFile: params.imageFile
1703
+ });
1704
+ }
1705
+ async execute(input, ctx) {
1706
+ return this.inner.execute(input, ctx);
1707
+ }
1708
+ };
1709
+ var StaticScriptSkill = class {
1710
+ name;
1711
+ description;
1712
+ capabilities;
1713
+ priceSubunits;
1714
+ asset;
1715
+ mode = "static-script";
1716
+ image;
1717
+ imageFile;
1718
+ dir;
1719
+ inner;
1720
+ constructor(params) {
1721
+ this.name = params.name;
1722
+ this.description = params.description;
1723
+ this.capabilities = params.capabilities;
1724
+ this.priceSubunits = params.priceSubunits;
1725
+ this.asset = params.asset;
1726
+ this.image = params.image;
1727
+ this.imageFile = params.imageFile;
1728
+ this.dir = params.dir;
1729
+ this.inner = new StaticScriptSkill$1({
1730
+ name: params.name,
1731
+ description: params.description,
1732
+ capabilities: params.capabilities,
1733
+ priceSubunits: BigInt(Math.round(params.priceSubunits)),
1734
+ asset: params.asset,
1735
+ scriptPath: params.scriptPath,
1736
+ scriptArgs: params.scriptArgs,
1737
+ scriptTimeoutMs: params.scriptTimeoutMs,
1738
+ image: params.image,
1739
+ imageFile: params.imageFile
1740
+ });
1741
+ }
1742
+ async execute(input, ctx) {
1743
+ return this.inner.execute(input, ctx);
1744
+ }
1745
+ };
1746
+ var DynamicScriptSkill = class {
1747
+ name;
1748
+ description;
1749
+ capabilities;
1750
+ priceSubunits;
1751
+ asset;
1752
+ mode = "dynamic-script";
1753
+ image;
1754
+ imageFile;
1755
+ dir;
1756
+ inner;
1757
+ constructor(params) {
1758
+ this.name = params.name;
1759
+ this.description = params.description;
1760
+ this.capabilities = params.capabilities;
1761
+ this.priceSubunits = params.priceSubunits;
1762
+ this.asset = params.asset;
1763
+ this.image = params.image;
1764
+ this.imageFile = params.imageFile;
1765
+ this.dir = params.dir;
1766
+ this.inner = new DynamicScriptSkill$1({
1767
+ name: params.name,
1768
+ description: params.description,
1769
+ capabilities: params.capabilities,
1770
+ priceSubunits: BigInt(Math.round(params.priceSubunits)),
1771
+ asset: params.asset,
1772
+ scriptPath: params.scriptPath,
1773
+ scriptArgs: params.scriptArgs,
1774
+ scriptTimeoutMs: params.scriptTimeoutMs,
1775
+ image: params.image,
1776
+ imageFile: params.imageFile
1777
+ });
1778
+ }
1779
+ async execute(input, ctx) {
1780
+ return this.inner.execute(input, ctx);
1781
+ }
1782
+ };
1663
1783
  var ScriptSkill = class {
1664
1784
  name;
1665
1785
  description;
1666
1786
  capabilities;
1667
1787
  priceSubunits;
1668
1788
  asset;
1789
+ mode = "llm";
1669
1790
  image;
1670
1791
  imageFile;
1671
1792
  dir;
@@ -1723,6 +1844,74 @@ var ScriptSkill = class {
1723
1844
  };
1724
1845
 
1725
1846
  // src/skill/loader.ts
1847
+ function buildCliSkill(parsed, entryPath) {
1848
+ switch (parsed.mode) {
1849
+ case "llm":
1850
+ return new ScriptSkill(
1851
+ parsed.name,
1852
+ parsed.description,
1853
+ parsed.capabilities,
1854
+ Number(parsed.priceSubunits),
1855
+ parsed.asset,
1856
+ parsed.image,
1857
+ parsed.imageFile,
1858
+ entryPath,
1859
+ parsed.systemPrompt,
1860
+ parsed.tools,
1861
+ parsed.maxToolRounds
1862
+ );
1863
+ case "static-file": {
1864
+ if (parsed.outputFile === void 0) {
1865
+ throw new Error(
1866
+ `SKILL.md "${parsed.name}": internal error - outputFile missing for mode 'static-file'`
1867
+ );
1868
+ }
1869
+ const outputFilePath = resolveInsidePath(entryPath, parsed.outputFile);
1870
+ if (!outputFilePath) {
1871
+ throw new Error(
1872
+ `SKILL.md "${parsed.name}": "output_file" must stay inside the skill directory`
1873
+ );
1874
+ }
1875
+ return new StaticFileSkill({
1876
+ name: parsed.name,
1877
+ description: parsed.description,
1878
+ capabilities: parsed.capabilities,
1879
+ priceSubunits: Number(parsed.priceSubunits),
1880
+ asset: parsed.asset,
1881
+ outputFilePath,
1882
+ image: parsed.image,
1883
+ imageFile: parsed.imageFile,
1884
+ dir: entryPath
1885
+ });
1886
+ }
1887
+ case "static-script":
1888
+ case "dynamic-script": {
1889
+ if (parsed.script === void 0) {
1890
+ throw new Error(
1891
+ `SKILL.md "${parsed.name}": internal error - script missing for mode '${parsed.mode}'`
1892
+ );
1893
+ }
1894
+ const scriptPath = resolveInsidePath(entryPath, parsed.script);
1895
+ if (!scriptPath) {
1896
+ throw new Error(`SKILL.md "${parsed.name}": "script" must stay inside the skill directory`);
1897
+ }
1898
+ const Ctor = parsed.mode === "static-script" ? StaticScriptSkill : DynamicScriptSkill;
1899
+ return new Ctor({
1900
+ name: parsed.name,
1901
+ description: parsed.description,
1902
+ capabilities: parsed.capabilities,
1903
+ priceSubunits: Number(parsed.priceSubunits),
1904
+ asset: parsed.asset,
1905
+ scriptPath,
1906
+ scriptArgs: parsed.scriptArgs,
1907
+ scriptTimeoutMs: parsed.scriptTimeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS,
1908
+ image: parsed.image,
1909
+ imageFile: parsed.imageFile,
1910
+ dir: entryPath
1911
+ });
1912
+ }
1913
+ }
1914
+ }
1726
1915
  function loadSkillsFromDir(skillsDir) {
1727
1916
  const skills = [];
1728
1917
  let entries;
@@ -1747,21 +1936,7 @@ function loadSkillsFromDir(skillsDir) {
1747
1936
  const parsed = validateSkillFrontmatter(frontmatter, systemPrompt, {
1748
1937
  allowFreeSkills: true
1749
1938
  });
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
- );
1939
+ skills.push(buildCliSkill(parsed, entryPath));
1765
1940
  } catch (e) {
1766
1941
  const message = e instanceof Error ? e.message : String(e);
1767
1942
  console.warn(` ! Skipping skill "${entry}": ${message}`);
@@ -2037,36 +2212,6 @@ async function cmdStart(nameArg, options = {}) {
2037
2212
  `);
2038
2213
  }
2039
2214
  }
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
2215
  const paths = agentPaths(loaded.dir);
2071
2216
  const skillsDir = paths.skills;
2072
2217
  const allSkills = loadSkillsFromDir(skillsDir);
@@ -2087,16 +2232,56 @@ async function cmdStart(nameArg, options = {}) {
2087
2232
  console.log();
2088
2233
  const hasPaid = allSkills.some((s) => s.priceSubunits > 0);
2089
2234
  if (hasPaid && !solanaAddress) {
2090
- console.error(" ! Paid skills require a Solana address. Run `elisym init` to configure.\n");
2235
+ console.error(
2236
+ " ! Paid skills require a Solana address. Run `npx @elisym/cli init` to configure.\n"
2237
+ );
2091
2238
  process.exit(1);
2092
2239
  }
2093
- const llm = createLlmClient({
2094
- provider: loaded.yaml.llm.provider,
2095
- apiKey: loaded.secrets.llm_api_key,
2096
- model: loaded.yaml.llm.model,
2097
- maxTokens: loaded.yaml.llm.max_tokens,
2098
- logUsage: true
2099
- });
2240
+ const hasLlmSkill = allSkills.some((skill) => skill.mode === "llm");
2241
+ let llm;
2242
+ if (hasLlmSkill) {
2243
+ if (!loaded.yaml.llm) {
2244
+ console.error(" ! No LLM configured. Run `npx @elisym/cli init` to set up LLM.\n");
2245
+ process.exit(1);
2246
+ }
2247
+ if (!loaded.secrets.llm_api_key) {
2248
+ console.error(
2249
+ ` ! No LLM API key. Set ${loaded.yaml.llm.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"} env var or update the agent's secrets.
2250
+ `
2251
+ );
2252
+ process.exit(1);
2253
+ }
2254
+ const llmProvider = loaded.yaml.llm.provider;
2255
+ const keyEnvVar = llmProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
2256
+ process.stdout.write(` Verifying ${llmProvider} API key... `);
2257
+ const verification = await verifyLlmApiKey(llmProvider, loaded.secrets.llm_api_key);
2258
+ if (verification.ok) {
2259
+ console.log("ok");
2260
+ } else if (verification.reason === "invalid") {
2261
+ console.log("INVALID");
2262
+ console.error(` ! ${llmProvider} rejected the API key (HTTP ${verification.status}).`);
2263
+ console.error(
2264
+ ` Update it via \`npx @elisym/cli profile\` or set ${keyEnvVar} to a valid key.
2265
+ `
2266
+ );
2267
+ process.exit(1);
2268
+ } else {
2269
+ console.log("unavailable");
2270
+ console.warn(
2271
+ ` ! Could not verify ${llmProvider} API key (${verification.error}). Continuing - jobs will fail if the key is invalid.
2272
+ `
2273
+ );
2274
+ }
2275
+ llm = createLlmClient({
2276
+ provider: loaded.yaml.llm.provider,
2277
+ apiKey: loaded.secrets.llm_api_key,
2278
+ model: loaded.yaml.llm.model,
2279
+ maxTokens: loaded.yaml.llm.max_tokens,
2280
+ logUsage: true
2281
+ });
2282
+ } else {
2283
+ console.log(" No LLM skills loaded; skipping LLM key check.\n");
2284
+ }
2100
2285
  const skillCtx = {
2101
2286
  llm,
2102
2287
  agentName,
@@ -2177,11 +2362,13 @@ async function cmdStart(nameArg, options = {}) {
2177
2362
  }
2178
2363
  const kinds = [jobRequestKind(DEFAULT_KIND_OFFSET)];
2179
2364
  function buildCard(skill) {
2365
+ const isStatic = skill.mode === "static-file" || skill.mode === "static-script";
2180
2366
  return {
2181
2367
  name: skill.name,
2182
2368
  description: skill.description,
2183
2369
  capabilities: skill.capabilities,
2184
2370
  image: skill.image,
2371
+ ...isStatic ? { static: true } : {},
2185
2372
  payment: solanaAddress ? {
2186
2373
  chain: "solana",
2187
2374
  network: walletNetwork,
@@ -2370,7 +2557,7 @@ async function resolveStartAgentName(nameArg, cwd) {
2370
2557
  }
2371
2558
  const agents = await listAgents(cwd);
2372
2559
  if (agents.length === 0) {
2373
- console.log("No agents configured. Run `elisym init` first.");
2560
+ console.log("No agents configured. Run `npx @elisym/cli init` first.");
2374
2561
  process.exit(1);
2375
2562
  }
2376
2563
  const { default: inquirer } = await import('inquirer');
@@ -2546,7 +2733,7 @@ program.command("list").description("List all agents (project-local and home-glo
2546
2733
  const cwd = process.cwd();
2547
2734
  const agents = await listAgents(cwd);
2548
2735
  if (agents.length === 0) {
2549
- console.log("No agents found. Run `elisym init` to create one.");
2736
+ console.log("No agents found. Run `npx @elisym/cli init` to create one.");
2550
2737
  return;
2551
2738
  }
2552
2739
  console.log("\nAgents:");