@elisym/cli 0.8.2 → 0.9.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 +9 -0
- package/dist/index.js +274 -63
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills-examples/README.md +2 -0
- package/skills-examples/cheap-summarizer/SKILL.md +26 -0
package/README.md
CHANGED
|
@@ -169,6 +169,9 @@ then return a concise overview and key points.
|
|
|
169
169
|
| `script_timeout_ms` | no | number | Hard timeout for the script in ms (positive integer). Default: `60000`. Script modes only. |
|
|
170
170
|
| `tools` | no | object[] | External scripts the LLM can call via tool-use. `mode: llm` only. |
|
|
171
171
|
| `max_tool_rounds` | no | number | Max LLM-tool interaction rounds per job. Default: `10`. `mode: llm` only. |
|
|
172
|
+
| `provider` | no | string | Per-skill LLM provider override (`anthropic` or `openai`). Must be set together with `model`. Falls back to agent-level `llm.provider`. `mode: llm` only. |
|
|
173
|
+
| `model` | no | string | Per-skill model override (e.g. `gpt-5-mini`, `claude-haiku-4-5-20251001`). Must be set together with `provider`. Falls back to agent-level `llm.model`. `mode: llm` only. |
|
|
174
|
+
| `max_tokens` | no | number | Per-skill max-tokens override (positive integer, max 200000). Independent of `provider`/`model`. Falls back to agent-level `llm.max_tokens`, then `4096`. `mode: llm` only. |
|
|
172
175
|
|
|
173
176
|
### Tool definition
|
|
174
177
|
|
|
@@ -206,6 +209,12 @@ Everything after the closing `---` becomes the LLM system prompt. Describe the a
|
|
|
206
209
|
|
|
207
210
|
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
211
|
|
|
212
|
+
### Per-skill model / provider overrides
|
|
213
|
+
|
|
214
|
+
Each `mode: llm` skill can route to a different model than the agent default. Declare `provider` + `model` (all-or-nothing) and/or `max_tokens` in the skill's frontmatter; whatever the skill omits inherits from `elisym.yaml` `llm`. If every LLM skill overrides fully, the agent-level `llm` block can be omitted entirely.
|
|
215
|
+
|
|
216
|
+
API keys live in `secrets.anthropic_api_key` / `secrets.openai_api_key` (encrypted at rest when a passphrase is set), or come from the matching `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` env var. `npx @elisym/cli init` and `profile` let you configure both keys, so a skill that overrides to a non-default provider can ship alongside the agent default. See [`skills-examples/cheap-summarizer/`](./skills-examples/cheap-summarizer/SKILL.md) for a working example.
|
|
217
|
+
|
|
209
218
|
Minimal non-LLM examples (drop `mode` to fall back to `llm`):
|
|
210
219
|
|
|
211
220
|
```markdown
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,6 @@ import { isAddress, createSolanaRpc, address } from '@solana/kit';
|
|
|
7
7
|
import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
|
|
8
8
|
import YAML from 'yaml';
|
|
9
9
|
import { Command } from 'commander';
|
|
10
|
-
import { isEncrypted } from '@elisym/sdk/node';
|
|
11
10
|
import { createHash } from 'node:crypto';
|
|
12
11
|
import { lookup } from 'node:dns/promises';
|
|
13
12
|
import { Socket } from 'node:net';
|
|
@@ -140,16 +139,16 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
140
139
|
yaml = result.yaml;
|
|
141
140
|
promptedApiKey = result.apiKey;
|
|
142
141
|
}
|
|
143
|
-
let
|
|
142
|
+
let defaultProviderKey;
|
|
144
143
|
if (yaml.llm) {
|
|
145
144
|
const envKey = yaml.llm.provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
|
|
146
145
|
if (envKey) {
|
|
147
|
-
|
|
146
|
+
defaultProviderKey = envKey;
|
|
148
147
|
console.log(
|
|
149
148
|
` Using ${yaml.llm.provider === "anthropic" ? "ANTHROPIC" : "OPENAI"}_API_KEY from environment.`
|
|
150
149
|
);
|
|
151
150
|
} else if (promptedApiKey) {
|
|
152
|
-
|
|
151
|
+
defaultProviderKey = promptedApiKey;
|
|
153
152
|
} else {
|
|
154
153
|
const { apiKey } = await inquirer.prompt([
|
|
155
154
|
{
|
|
@@ -159,7 +158,38 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
159
158
|
mask: "*"
|
|
160
159
|
}
|
|
161
160
|
]);
|
|
162
|
-
|
|
161
|
+
defaultProviderKey = apiKey || void 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
let otherProviderKey;
|
|
165
|
+
if (yaml.llm && !template) {
|
|
166
|
+
const otherProvider = yaml.llm.provider === "anthropic" ? "openai" : "anthropic";
|
|
167
|
+
const otherProviderLabel = otherProvider === "anthropic" ? "Anthropic" : "OpenAI";
|
|
168
|
+
const otherEnvVar = otherProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
169
|
+
const otherEnvKey = process.env[otherEnvVar];
|
|
170
|
+
const { configureOther } = await inquirer.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: "confirm",
|
|
173
|
+
name: "configureOther",
|
|
174
|
+
message: `Configure ${otherProviderLabel} API key too (for skills that override the default provider)?`,
|
|
175
|
+
default: Boolean(otherEnvKey)
|
|
176
|
+
}
|
|
177
|
+
]);
|
|
178
|
+
if (configureOther) {
|
|
179
|
+
if (otherEnvKey) {
|
|
180
|
+
otherProviderKey = otherEnvKey;
|
|
181
|
+
console.log(` Using ${otherEnvVar} from environment.`);
|
|
182
|
+
} else {
|
|
183
|
+
const { promptedOther } = await inquirer.prompt([
|
|
184
|
+
{
|
|
185
|
+
type: "password",
|
|
186
|
+
name: "promptedOther",
|
|
187
|
+
message: `${otherProviderLabel} API key:`,
|
|
188
|
+
mask: "*"
|
|
189
|
+
}
|
|
190
|
+
]);
|
|
191
|
+
otherProviderKey = promptedOther || void 0;
|
|
192
|
+
}
|
|
163
193
|
}
|
|
164
194
|
}
|
|
165
195
|
let passphrase = "";
|
|
@@ -194,12 +224,15 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
194
224
|
const nostrPubkey = getPublicKey(nostrSecretBytes);
|
|
195
225
|
const nostrSecretHex = Buffer.from(nostrSecretBytes).toString("hex");
|
|
196
226
|
const created = await createAgentDir({ target, name: agentName, cwd });
|
|
227
|
+
const anthropicKey = yaml.llm?.provider === "anthropic" ? defaultProviderKey : otherProviderKey;
|
|
228
|
+
const openaiKey = yaml.llm?.provider === "openai" ? defaultProviderKey : otherProviderKey;
|
|
197
229
|
await writeYaml(created.dir, yaml);
|
|
198
230
|
await writeSecrets(
|
|
199
231
|
created.dir,
|
|
200
232
|
{
|
|
201
233
|
nostr_secret_key: nostrSecretHex,
|
|
202
|
-
|
|
234
|
+
anthropic_api_key: anthropicKey,
|
|
235
|
+
openai_api_key: openaiKey
|
|
203
236
|
},
|
|
204
237
|
passphrase || void 0
|
|
205
238
|
);
|
|
@@ -490,7 +523,7 @@ async function cmdProfile(name) {
|
|
|
490
523
|
{
|
|
491
524
|
type: "list",
|
|
492
525
|
name: "llmProvider",
|
|
493
|
-
message: "LLM provider:",
|
|
526
|
+
message: "Default LLM provider (used by skills without a `provider:` override):",
|
|
494
527
|
choices: [
|
|
495
528
|
{ name: "Anthropic (Claude)", value: "anthropic" },
|
|
496
529
|
{ name: "OpenAI (GPT)", value: "openai" }
|
|
@@ -498,16 +531,17 @@ async function cmdProfile(name) {
|
|
|
498
531
|
default: loaded.yaml.llm?.provider ?? "anthropic"
|
|
499
532
|
}
|
|
500
533
|
]);
|
|
534
|
+
const otherProvider = llmProvider === "anthropic" ? "openai" : "anthropic";
|
|
535
|
+
const defaultKeyStatus = describeKeyStatus(loaded.secrets, llmProvider);
|
|
501
536
|
const { apiKey } = await inquirer.prompt([
|
|
502
537
|
{
|
|
503
538
|
type: "password",
|
|
504
539
|
name: "apiKey",
|
|
505
|
-
message:
|
|
540
|
+
message: `${labelFor(llmProvider)} API key [${defaultKeyStatus}] (leave empty to keep current):`,
|
|
506
541
|
mask: "*"
|
|
507
542
|
}
|
|
508
543
|
]);
|
|
509
|
-
const
|
|
510
|
-
const probeKey = apiKey || currentKeyPlain;
|
|
544
|
+
const probeKey = apiKey || pickPlainKey(loaded.secrets, llmProvider);
|
|
511
545
|
console.log(" Fetching available models...");
|
|
512
546
|
const { fetchModels: fetchModels2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
513
547
|
const models = await fetchModels2(llmProvider, probeKey);
|
|
@@ -528,13 +562,37 @@ async function cmdProfile(name) {
|
|
|
528
562
|
default: loaded.yaml.llm?.max_tokens ?? 4096
|
|
529
563
|
}
|
|
530
564
|
]);
|
|
565
|
+
const otherKeyStatus = describeKeyStatus(loaded.secrets, otherProvider);
|
|
566
|
+
const { otherApiKey } = await inquirer.prompt([
|
|
567
|
+
{
|
|
568
|
+
type: "password",
|
|
569
|
+
name: "otherApiKey",
|
|
570
|
+
message: `${labelFor(otherProvider)} API key for skill overrides [${otherKeyStatus}] (leave empty to keep current):`,
|
|
571
|
+
mask: "*"
|
|
572
|
+
}
|
|
573
|
+
]);
|
|
531
574
|
const nextLlm = { provider: llmProvider, model, max_tokens: maxTokens };
|
|
532
575
|
const nextYaml = { ...loaded.yaml, llm: nextLlm };
|
|
533
576
|
await writeYaml(loaded.dir, nextYaml);
|
|
534
577
|
loaded.yaml = nextYaml;
|
|
535
|
-
if (apiKey) {
|
|
536
|
-
|
|
537
|
-
|
|
578
|
+
if (apiKey || otherApiKey) {
|
|
579
|
+
const nextSecrets = { ...loaded.secrets };
|
|
580
|
+
if (apiKey) {
|
|
581
|
+
if (llmProvider === "anthropic") {
|
|
582
|
+
nextSecrets.anthropic_api_key = apiKey;
|
|
583
|
+
} else {
|
|
584
|
+
nextSecrets.openai_api_key = apiKey;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (otherApiKey) {
|
|
588
|
+
if (otherProvider === "anthropic") {
|
|
589
|
+
nextSecrets.anthropic_api_key = otherApiKey;
|
|
590
|
+
} else {
|
|
591
|
+
nextSecrets.openai_api_key = otherApiKey;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
await writeSecrets(loaded.dir, nextSecrets, passphrase);
|
|
595
|
+
loaded.secrets = nextSecrets;
|
|
538
596
|
}
|
|
539
597
|
console.log(" LLM updated.\n");
|
|
540
598
|
}
|
|
@@ -548,6 +606,17 @@ function truncate(value, max = 40) {
|
|
|
548
606
|
}
|
|
549
607
|
return value.slice(0, max - 1) + "\u2026";
|
|
550
608
|
}
|
|
609
|
+
function labelFor(provider) {
|
|
610
|
+
return provider === "anthropic" ? "Anthropic" : "OpenAI";
|
|
611
|
+
}
|
|
612
|
+
function describeKeyStatus(secrets, provider) {
|
|
613
|
+
const perProviderField = provider === "anthropic" ? "anthropic_api_key" : "openai_api_key";
|
|
614
|
+
return secrets[perProviderField] ? "set" : "not set";
|
|
615
|
+
}
|
|
616
|
+
function pickPlainKey(secrets, provider) {
|
|
617
|
+
const perProviderField = provider === "anthropic" ? "anthropic_api_key" : "openai_api_key";
|
|
618
|
+
return secrets[perProviderField] ?? "";
|
|
619
|
+
}
|
|
551
620
|
var TCP_PROBE_TIMEOUT_MS = 3e3;
|
|
552
621
|
function parseRelayUrl(url) {
|
|
553
622
|
try {
|
|
@@ -1095,6 +1164,82 @@ var OpenAIClient = class {
|
|
|
1095
1164
|
}));
|
|
1096
1165
|
}
|
|
1097
1166
|
};
|
|
1167
|
+
|
|
1168
|
+
// src/llm/resolve.ts
|
|
1169
|
+
var DEFAULT_MAX_TOKENS = 4096;
|
|
1170
|
+
function resolveSkillLlm(input, agentDefault) {
|
|
1171
|
+
const override = input.llmOverride;
|
|
1172
|
+
const overridePairSet = override?.provider !== void 0 && override.model !== void 0;
|
|
1173
|
+
let provider;
|
|
1174
|
+
let model;
|
|
1175
|
+
if (overridePairSet) {
|
|
1176
|
+
provider = override.provider;
|
|
1177
|
+
model = override.model;
|
|
1178
|
+
} else if (agentDefault) {
|
|
1179
|
+
provider = agentDefault.provider;
|
|
1180
|
+
model = agentDefault.model;
|
|
1181
|
+
}
|
|
1182
|
+
if (!provider || !model) {
|
|
1183
|
+
return {
|
|
1184
|
+
error: `Skill "${input.skillName}" at ${input.skillMdPath}: LLM model is required - declare "provider" + "model" in the SKILL.md frontmatter or set agent-level llm via 'npx @elisym/cli profile <agent>'.`
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
1188
|
+
return { provider, model, maxTokens };
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/llm/cache.ts
|
|
1192
|
+
function cacheKeyFor(triple) {
|
|
1193
|
+
return JSON.stringify({
|
|
1194
|
+
provider: triple.provider,
|
|
1195
|
+
model: triple.model,
|
|
1196
|
+
maxTokens: triple.maxTokens
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
function resolveTripleForOverride(override, agentDefault) {
|
|
1200
|
+
const overridePairSet = override?.provider !== void 0 && override.model !== void 0;
|
|
1201
|
+
let provider;
|
|
1202
|
+
let model;
|
|
1203
|
+
if (overridePairSet) {
|
|
1204
|
+
provider = override.provider;
|
|
1205
|
+
model = override.model;
|
|
1206
|
+
} else if (agentDefault) {
|
|
1207
|
+
provider = agentDefault.provider;
|
|
1208
|
+
model = agentDefault.model;
|
|
1209
|
+
}
|
|
1210
|
+
if (!provider || !model) {
|
|
1211
|
+
return void 0;
|
|
1212
|
+
}
|
|
1213
|
+
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
1214
|
+
return { provider, model, maxTokens };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/llm/keys.ts
|
|
1218
|
+
var ENV_VAR_FOR_PROVIDER = {
|
|
1219
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
1220
|
+
openai: "OPENAI_API_KEY"
|
|
1221
|
+
};
|
|
1222
|
+
var SECRET_FIELD_FOR_PROVIDER = {
|
|
1223
|
+
anthropic: "anthropic_api_key",
|
|
1224
|
+
openai: "openai_api_key"
|
|
1225
|
+
};
|
|
1226
|
+
function resolveProviderApiKey(input) {
|
|
1227
|
+
const { provider, secrets, dependentSkills } = input;
|
|
1228
|
+
const perProviderField = SECRET_FIELD_FOR_PROVIDER[provider];
|
|
1229
|
+
const perProviderValue = secrets[perProviderField];
|
|
1230
|
+
if (perProviderValue) {
|
|
1231
|
+
return { apiKey: perProviderValue, origin: "secrets-per-provider" };
|
|
1232
|
+
}
|
|
1233
|
+
const envVar = ENV_VAR_FOR_PROVIDER[provider];
|
|
1234
|
+
const envValue = process.env[envVar];
|
|
1235
|
+
if (envValue) {
|
|
1236
|
+
return { apiKey: envValue, origin: "env" };
|
|
1237
|
+
}
|
|
1238
|
+
const skillList = dependentSkills.length > 0 ? dependentSkills.join(", ") : "<none>";
|
|
1239
|
+
return {
|
|
1240
|
+
error: `Provider "${provider}" needs an API key (required by skill(s): ${skillList}). Set secrets.${perProviderField} via 'npx @elisym/cli profile <agent>' or export ${envVar}.`
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1098
1243
|
function resolveLevel(options) {
|
|
1099
1244
|
if (options.level) {
|
|
1100
1245
|
return options.level;
|
|
@@ -1879,11 +2024,12 @@ var ScriptSkill = class {
|
|
|
1879
2024
|
priceSubunits;
|
|
1880
2025
|
asset;
|
|
1881
2026
|
mode = "llm";
|
|
2027
|
+
llmOverride;
|
|
1882
2028
|
image;
|
|
1883
2029
|
imageFile;
|
|
1884
2030
|
dir;
|
|
1885
2031
|
inner;
|
|
1886
|
-
constructor(name, description, capabilities, priceSubunits, assetOrImage, imageOrImageFile, imageFileOrDir, dirOrPrompt, promptOrTools, toolsOrRounds, rounds) {
|
|
2032
|
+
constructor(name, description, capabilities, priceSubunits, assetOrImage, imageOrImageFile, imageFileOrDir, dirOrPrompt, promptOrTools, toolsOrRounds, rounds, llmOverride) {
|
|
1887
2033
|
let asset;
|
|
1888
2034
|
let image;
|
|
1889
2035
|
let imageFile;
|
|
@@ -1913,6 +2059,7 @@ var ScriptSkill = class {
|
|
|
1913
2059
|
this.capabilities = capabilities;
|
|
1914
2060
|
this.priceSubunits = priceSubunits;
|
|
1915
2061
|
this.asset = asset;
|
|
2062
|
+
this.llmOverride = llmOverride;
|
|
1916
2063
|
this.image = image;
|
|
1917
2064
|
this.imageFile = imageFile;
|
|
1918
2065
|
this.dir = skillDir;
|
|
@@ -1926,6 +2073,7 @@ var ScriptSkill = class {
|
|
|
1926
2073
|
systemPrompt,
|
|
1927
2074
|
tools,
|
|
1928
2075
|
maxToolRounds,
|
|
2076
|
+
llmOverride,
|
|
1929
2077
|
image,
|
|
1930
2078
|
imageFile
|
|
1931
2079
|
});
|
|
@@ -1950,7 +2098,8 @@ function buildCliSkill(parsed, entryPath) {
|
|
|
1950
2098
|
entryPath,
|
|
1951
2099
|
parsed.systemPrompt,
|
|
1952
2100
|
parsed.tools,
|
|
1953
|
-
parsed.maxToolRounds
|
|
2101
|
+
parsed.maxToolRounds,
|
|
2102
|
+
parsed.llmOverride
|
|
1954
2103
|
);
|
|
1955
2104
|
case "static-file": {
|
|
1956
2105
|
if (parsed.outputFile === void 0) {
|
|
@@ -2036,7 +2185,7 @@ function loadSkillsFromDir(skillsDir) {
|
|
|
2036
2185
|
}
|
|
2037
2186
|
return skills;
|
|
2038
2187
|
}
|
|
2039
|
-
function
|
|
2188
|
+
function isEncrypted(event) {
|
|
2040
2189
|
return event.tags.some((t) => t[0] === "encrypted" && t[1] === "nip44");
|
|
2041
2190
|
}
|
|
2042
2191
|
var HEALTH_CHECK_IDLE_MS = 30 * 60 * 1e3;
|
|
@@ -2070,7 +2219,7 @@ var NostrTransport = class {
|
|
|
2070
2219
|
}
|
|
2071
2220
|
const tags = event.tags.filter((t) => t[0] === "t").map((t) => t[1]);
|
|
2072
2221
|
const bidTag = event.tags.find((t) => t[0] === "bid");
|
|
2073
|
-
const encrypted =
|
|
2222
|
+
const encrypted = isEncrypted(event);
|
|
2074
2223
|
const iTag = event.tags.find((t) => t[0] === "i");
|
|
2075
2224
|
let inputType = "text";
|
|
2076
2225
|
if (iTag?.[2]) {
|
|
@@ -2399,62 +2548,124 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2399
2548
|
);
|
|
2400
2549
|
process.exit(1);
|
|
2401
2550
|
}
|
|
2402
|
-
const
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2551
|
+
const llmSkills = allSkills.filter((skill) => skill.mode === "llm");
|
|
2552
|
+
const triplesByKey = /* @__PURE__ */ new Map();
|
|
2553
|
+
const dependentSkillsByProvider = /* @__PURE__ */ new Map();
|
|
2554
|
+
let agentDefaultCacheKey;
|
|
2555
|
+
const llmClientCache = /* @__PURE__ */ new Map();
|
|
2556
|
+
if (llmSkills.length > 0) {
|
|
2557
|
+
const resolutionErrors = [];
|
|
2558
|
+
for (const skill of llmSkills) {
|
|
2559
|
+
const skillMdPath = join(skillsDir, skill.name, "SKILL.md");
|
|
2560
|
+
const result = resolveSkillLlm(
|
|
2561
|
+
{ skillName: skill.name, skillMdPath, llmOverride: skill.llmOverride },
|
|
2562
|
+
loaded.yaml.llm
|
|
2413
2563
|
);
|
|
2564
|
+
if ("error" in result) {
|
|
2565
|
+
resolutionErrors.push(result.error);
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
const cacheKey = cacheKeyFor(result);
|
|
2569
|
+
triplesByKey.set(cacheKey, result);
|
|
2570
|
+
const list = dependentSkillsByProvider.get(result.provider) ?? [];
|
|
2571
|
+
list.push(skill.name);
|
|
2572
|
+
dependentSkillsByProvider.set(result.provider, list);
|
|
2573
|
+
}
|
|
2574
|
+
if (resolutionErrors.length > 0) {
|
|
2575
|
+
for (const message of resolutionErrors) {
|
|
2576
|
+
console.error(` ! ${message}`);
|
|
2577
|
+
}
|
|
2578
|
+
console.error("");
|
|
2414
2579
|
process.exit(1);
|
|
2415
2580
|
}
|
|
2416
|
-
if (
|
|
2417
|
-
const
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
);
|
|
2581
|
+
if (loaded.yaml.llm) {
|
|
2582
|
+
const agentDefaultTriple = {
|
|
2583
|
+
provider: loaded.yaml.llm.provider,
|
|
2584
|
+
model: loaded.yaml.llm.model,
|
|
2585
|
+
maxTokens: loaded.yaml.llm.max_tokens
|
|
2586
|
+
};
|
|
2587
|
+
const agentKey = cacheKeyFor(agentDefaultTriple);
|
|
2588
|
+
if (triplesByKey.has(agentKey)) {
|
|
2589
|
+
agentDefaultCacheKey = agentKey;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
const keyByProvider = /* @__PURE__ */ new Map();
|
|
2593
|
+
const keyErrors = [];
|
|
2594
|
+
for (const [provider, dependents] of dependentSkillsByProvider) {
|
|
2595
|
+
const keyResult = resolveProviderApiKey({
|
|
2596
|
+
provider,
|
|
2597
|
+
secrets: loaded.secrets,
|
|
2598
|
+
dependentSkills: dependents
|
|
2599
|
+
});
|
|
2600
|
+
if ("error" in keyResult) {
|
|
2601
|
+
keyErrors.push(keyResult.error);
|
|
2602
|
+
continue;
|
|
2603
|
+
}
|
|
2604
|
+
keyByProvider.set(provider, keyResult.apiKey);
|
|
2605
|
+
}
|
|
2606
|
+
if (keyErrors.length > 0) {
|
|
2607
|
+
for (const message of keyErrors) {
|
|
2608
|
+
console.error(` ! ${message}`);
|
|
2609
|
+
}
|
|
2610
|
+
console.error("");
|
|
2423
2611
|
process.exit(1);
|
|
2424
2612
|
}
|
|
2425
|
-
const
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2613
|
+
for (const provider of keyByProvider.keys()) {
|
|
2614
|
+
const apiKey = keyByProvider.get(provider);
|
|
2615
|
+
if (!apiKey) {
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
const envVar = provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
2619
|
+
process.stdout.write(` Verifying ${provider} API key... `);
|
|
2620
|
+
const verification = await verifyLlmApiKey(provider, apiKey);
|
|
2621
|
+
if (verification.ok) {
|
|
2622
|
+
console.log("ok");
|
|
2623
|
+
} else if (verification.reason === "invalid") {
|
|
2624
|
+
console.log("INVALID");
|
|
2625
|
+
console.error(` ! ${provider} rejected the API key (HTTP ${verification.status}).`);
|
|
2626
|
+
console.error(
|
|
2627
|
+
` Update it via \`npx @elisym/cli profile ${agentName}\` or set ${envVar} to a valid key.
|
|
2436
2628
|
`
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2629
|
+
);
|
|
2630
|
+
process.exit(1);
|
|
2631
|
+
} else {
|
|
2632
|
+
console.log("unavailable");
|
|
2633
|
+
console.warn(
|
|
2634
|
+
` ! Could not verify ${provider} API key (${verification.error}). Continuing - jobs will fail if the key is invalid.
|
|
2443
2635
|
`
|
|
2444
|
-
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
for (const [cacheKey, triple] of triplesByKey) {
|
|
2640
|
+
const apiKey = keyByProvider.get(triple.provider);
|
|
2641
|
+
if (!apiKey) {
|
|
2642
|
+
console.error(` ! Internal error: no API key for provider "${triple.provider}".
|
|
2643
|
+
`);
|
|
2644
|
+
process.exit(1);
|
|
2645
|
+
}
|
|
2646
|
+
const config = {
|
|
2647
|
+
provider: triple.provider,
|
|
2648
|
+
apiKey,
|
|
2649
|
+
model: triple.model,
|
|
2650
|
+
maxTokens: triple.maxTokens,
|
|
2651
|
+
logUsage: true
|
|
2652
|
+
};
|
|
2653
|
+
llmClientCache.set(cacheKey, createLlmClient(config));
|
|
2445
2654
|
}
|
|
2446
|
-
llm = createLlmClient({
|
|
2447
|
-
provider: loaded.yaml.llm.provider,
|
|
2448
|
-
apiKey: loaded.secrets.llm_api_key,
|
|
2449
|
-
model: loaded.yaml.llm.model,
|
|
2450
|
-
maxTokens: loaded.yaml.llm.max_tokens,
|
|
2451
|
-
logUsage: true
|
|
2452
|
-
});
|
|
2453
2655
|
} else {
|
|
2454
2656
|
console.log(" No LLM skills loaded; skipping LLM key check.\n");
|
|
2455
2657
|
}
|
|
2658
|
+
const agentDefaultClient = agentDefaultCacheKey !== void 0 ? llmClientCache.get(agentDefaultCacheKey) : void 0;
|
|
2659
|
+
const getLlm = (override) => {
|
|
2660
|
+
const triple = resolveTripleForOverride(override, loaded.yaml.llm);
|
|
2661
|
+
if (!triple) {
|
|
2662
|
+
return void 0;
|
|
2663
|
+
}
|
|
2664
|
+
return llmClientCache.get(cacheKeyFor(triple));
|
|
2665
|
+
};
|
|
2456
2666
|
const skillCtx = {
|
|
2457
|
-
llm,
|
|
2667
|
+
llm: agentDefaultClient,
|
|
2668
|
+
getLlm,
|
|
2458
2669
|
agentName,
|
|
2459
2670
|
agentDescription: loaded.yaml.description ?? ""
|
|
2460
2671
|
};
|
|
@@ -2754,9 +2965,9 @@ async function loadAgentWithPrompt(name, cwd) {
|
|
|
2754
2965
|
try {
|
|
2755
2966
|
return await loadAgent(name, cwd, envPassphrase);
|
|
2756
2967
|
} catch (e) {
|
|
2757
|
-
const
|
|
2968
|
+
const isEncrypted2 = /encrypted secrets/i.test(e?.message ?? "");
|
|
2758
2969
|
const isWrongPassphrase = /Decryption failed/i.test(e?.message ?? "");
|
|
2759
|
-
if (!
|
|
2970
|
+
if (!isEncrypted2 && !isWrongPassphrase) {
|
|
2760
2971
|
throw e;
|
|
2761
2972
|
}
|
|
2762
2973
|
if (!process.stdin.isTTY) {
|