skillfoxx 0.1.0 → 0.1.2

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.en.md CHANGED
@@ -16,7 +16,7 @@ Requires Node.js 18.18 or newer.
16
16
  | `remove <entry>` | removes what SkillFoxx installed |
17
17
  | `update [entry]` | updates to the version SkillFoxx has rechecked |
18
18
  | `list` | shows what is installed |
19
- | `doctor` | checks installs: edited by hand, outdated, removed from the catalog |
19
+ | `doctor` | checks installs: edited by hand, outdated, removed from the catalog, environment variables not set |
20
20
  | `search <query>` | searches the catalog |
21
21
 
22
22
  Options: `--agent claude-code,cursor` (or `all`), `--project`, `--global`, `-y`/`--yes`, `--force`, `--dry-run`, `--json`, `--lang ru|en`.
@@ -41,6 +41,7 @@ Without `--agent` the CLI picks the agent it runs inside, otherwise every agent
41
41
  - A key SkillFoxx did not install, or one edited by hand, is not replaced without `--force`.
42
42
  - In a project a secret never goes into a file: the CLI writes an environment variable reference if the agent supports it, otherwise it prints instructions and suggests installing with `--global`.
43
43
  - For the user scope the secret value goes into the agent config, the way the agents do it themselves. Lock files hold no values.
44
+ - `doctor` checks required variables against the current environment (names and status only, no values; secrets are listed first among problems). On macOS an app opened from the Dock or Launchpad does not see exports from `~/.zshrc`: after `add`, such variables get a hint about `launchctl setenv` or starting the agent from a terminal where the variable is already set.
44
45
 
45
46
  ## Lock files
46
47
 
package/README.md CHANGED
@@ -16,7 +16,7 @@ npx skillfoxx add mcp/vv-mcp-server
16
16
  | `remove <запись>` | удаляет то, что поставил SkillFoxx |
17
17
  | `update [запись]` | обновляет до версии, которую сверил SkillFoxx |
18
18
  | `list` | показывает установленное |
19
- | `doctor` | проверяет: изменено руками, устарело, снято из каталога |
19
+ | `doctor` | проверяет: изменено руками, устарело, снято из каталога, не заданы переменные окружения |
20
20
  | `search <запрос>` | ищет в каталоге |
21
21
 
22
22
  Параметры: `--agent claude-code,cursor` (или `all`), `--project`, `--global`, `-y`/`--yes`, `--force`, `--dry-run`, `--json`, `--lang ru|en`.
@@ -41,6 +41,7 @@ Claude Code, Cursor, VS Code, Codex, Gemini CLI, Devin, Cline, Zoo Code, OpenCod
41
41
  - Ключ, который поставил не SkillFoxx или который изменили руками, без `--force` не заменяется.
42
42
  - В проекте секрет не пишется в файл никогда: пишется ссылка на переменную окружения, если агент ее понимает, иначе печатается инструкция и совет поставить с `--global`.
43
43
  - Для пользователя значение секрета пишется в конфиг агента, как это делают сами агенты. В lock-файле значений нет.
44
+ - `doctor` сверяет обязательные переменные записей с текущим окружением (только имена и статус, без значений; секреты в списке проблем идут первыми). На macOS приложение, открытое из Dock или Launchpad, не видит экспорт из `~/.zshrc`: после `add` для таких переменных печатается подсказка про `launchctl setenv` или запуск агента из терминала, где переменная уже задана.
44
45
 
45
46
  ## Lock-файлы
46
47
 
package/dist/cli.js CHANGED
@@ -7371,7 +7371,7 @@ var require_dist = __commonJS({
7371
7371
  import { parseArgs } from "node:util";
7372
7372
 
7373
7373
  // src/context.ts
7374
- import { execFile } from "node:child_process";
7374
+ import { execFile, execFileSync } from "node:child_process";
7375
7375
  import { constants } from "node:fs";
7376
7376
  import { access } from "node:fs/promises";
7377
7377
  import os from "node:os";
@@ -19980,7 +19980,28 @@ var CliError = class extends Error {
19980
19980
  }
19981
19981
  };
19982
19982
  var tr = (ctx, ru, en) => ctx.lang === "ru" ? ru : en;
19983
- var detectLang = (env) => /^ru/i.test(env.LC_ALL || env.LC_MESSAGES || env.LANG || "") ? "ru" : "en";
19983
+ var detectLang = (env, systemLocale2 = () => null) => {
19984
+ const fromEnv = [env.LC_ALL, env.LC_MESSAGES, env.LANG].find((v) => v && !/^(C|POSIX)(\.|$)/i.test(v));
19985
+ const locale = fromEnv ?? systemLocale2();
19986
+ if (!locale) return "ru";
19987
+ return /^ru/i.test(locale) ? "ru" : "en";
19988
+ };
19989
+ var systemLocale = (platform) => {
19990
+ if (platform === "darwin") {
19991
+ try {
19992
+ const out = execFileSync("defaults", ["read", "-g", "AppleLanguages"], { encoding: "utf8", timeout: 500, stdio: ["ignore", "pipe", "ignore"] });
19993
+ const first = out.match(/"?([A-Za-z]{2}(?:[-_][A-Za-z0-9]+)*)"?/);
19994
+ if (first) return first[1];
19995
+ } catch {
19996
+ }
19997
+ }
19998
+ try {
19999
+ const loc = Intl.DateTimeFormat().resolvedOptions().locale;
20000
+ return loc && !/^en(-US)?$/i.test(loc) ? loc : null;
20001
+ } catch {
20002
+ return null;
20003
+ }
20004
+ };
19984
20005
  var maskedWrite = (question, output2) => (s) => {
19985
20006
  if (s.includes(question)) output2.write(question);
19986
20007
  };
@@ -20025,7 +20046,7 @@ var makeCtx = (over = {}) => {
20025
20046
  platform: process.platform,
20026
20047
  fetch: globalThis.fetch.bind(globalThis),
20027
20048
  tty: Boolean(process.stdin.isTTY && process.stdout.isTTY),
20028
- lang: detectLang(env),
20049
+ lang: detectLang(env, () => systemLocale(process.platform)),
20029
20050
  api: DEFAULT_API,
20030
20051
  keys: TRUSTED_RECIPE_KEYS,
20031
20052
  out: (line) => void process.stdout.write(`${line}
@@ -20057,7 +20078,7 @@ var findOnPath = async (ctx, bin) => {
20057
20078
  };
20058
20079
 
20059
20080
  // src/version.ts
20060
- var VERSION = true ? "0.1.0" : "0.0.0-dev";
20081
+ var VERSION = true ? "0.1.2" : "0.0.0-dev";
20061
20082
 
20062
20083
  // src/api.ts
20063
20084
  var DAY = 24 * 36e5;
@@ -22444,6 +22465,33 @@ var describePlan = (ctx, actions, root) => actions.map((a) => {
22444
22465
  var planJson = (actions, root) => actions.map(
22445
22466
  (a) => a.type === "mcp" ? { type: a.type, agent: a.agent, component: a.component.name, file: path5.relative(root, a.target.file), key: a.target.key } : { type: a.type, agent: "agent" in a ? a.agent : null, component: componentName(a.component), ...a.type === "manual" ? { reason: a.reason.en } : {} }
22446
22467
  );
22468
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
22469
+ var isLocalHost = (url2) => {
22470
+ try {
22471
+ const host = new URL(url2).hostname.toLowerCase();
22472
+ return LOCAL_HOSTS.has(host) || host.endsWith(".localhost");
22473
+ } catch {
22474
+ return false;
22475
+ }
22476
+ };
22477
+ var remoteAuthWarnings = (ctx, recipe) => {
22478
+ const warnings = [];
22479
+ for (const c of recipe.components) {
22480
+ if (c.kind !== "mcp-http" || isLocalHost(c.url)) continue;
22481
+ const authVars = new Set(c.headers.filter((h) => h.secret).flatMap((h) => templateVars(h.value)));
22482
+ const missing = [...authVars].filter((name2) => !ctx.env[name2]);
22483
+ if (missing.length) {
22484
+ warnings.push(
22485
+ tr(
22486
+ ctx,
22487
+ `${clean(c.name)}: \u0441\u0435\u0440\u0432\u0435\u0440 ${clean(c.url)} \u0431\u0443\u0434\u0435\u0442 \u0432\u044B\u0437\u0432\u0430\u043D \u0431\u0435\u0437 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u043F\u043E\u043A\u0430 \u043D\u0435 \u0437\u0430\u0434\u0430\u043D\u0430 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F ${missing.join(", ")}.`,
22488
+ `${clean(c.name)}: the server ${clean(c.url)} will be called without authorization until the environment variable ${missing.join(", ")} is set.`
22489
+ )
22490
+ );
22491
+ }
22492
+ }
22493
+ return warnings;
22494
+ };
22447
22495
  var manualNote = (ctx, action, entryUrl) => {
22448
22496
  const c = action.component;
22449
22497
  const who = action.agent ? agentDef(action.agent).title : tr(ctx, "\u0412\u0440\u0443\u0447\u043D\u0443\u044E", "By hand");
@@ -22548,6 +22596,14 @@ var downloadSkill = async (ctx, source, loaded, component2) => {
22548
22596
  };
22549
22597
 
22550
22598
  // src/lock.ts
22599
+ var recipeVars = (recipe) => {
22600
+ const out = /* @__PURE__ */ new Map();
22601
+ for (const c of recipe.components) {
22602
+ if (c.kind !== "mcp-stdio" && c.kind !== "mcp-http") continue;
22603
+ for (const e of c.env) if (!out.has(e.name)) out.set(e.name, { name: e.name, required: e.required, secret: e.secret });
22604
+ }
22605
+ return [...out.values()];
22606
+ };
22551
22607
  var LOCK_FILE = "skillfoxx-lock.json";
22552
22608
  var exists2 = async (p) => Boolean(await stat4(p).catch(() => null));
22553
22609
  var upward = async (from, markers) => {
@@ -22584,6 +22640,7 @@ var lockPathOk = (scope, p) => {
22584
22640
  const abs = p.replace(/^[A-Za-z]:/, "");
22585
22641
  return abs.startsWith("/") && safeRelative(abs.slice(1));
22586
22642
  };
22643
+ var varOk = (v) => isObj(v) && isStr(v.name) && VAR_NAME.test(v.name) && typeof v.required === "boolean" && typeof v.secret === "boolean";
22587
22644
  var itemOk = (scope, i) => {
22588
22645
  if (!isObj(i)) return false;
22589
22646
  switch (i.kind) {
@@ -22604,7 +22661,7 @@ var itemOk = (scope, i) => {
22604
22661
  var entryOk = (scope, key, e) => {
22605
22662
  const m = ENTRY_KEY.exec(key);
22606
22663
  if (!m || !isObj(e)) return false;
22607
- return e.section === m[1] && e.slug === m[2] && e.scope === scope && isStr(e.recipeHash) && strOrNull(e.commitSha) && strOrNull(e.contentDigest) && isStr(e.status) && strOrNull(e.risk) && Array.isArray(e.agents) && e.agents.every(isAgent) && Array.isArray(e.items) && e.items.every((i) => itemOk(scope, i)) && isStr(e.installedAt) && isStr(e.updatedAt) && isStr(e.cliVersion);
22664
+ return e.section === m[1] && e.slug === m[2] && e.scope === scope && isStr(e.recipeHash) && strOrNull(e.commitSha) && strOrNull(e.contentDigest) && isStr(e.status) && strOrNull(e.risk) && Array.isArray(e.agents) && e.agents.every(isAgent) && Array.isArray(e.items) && e.items.every((i) => itemOk(scope, i)) && (e.vars === void 0 || Array.isArray(e.vars) && e.vars.every(varOk)) && isStr(e.installedAt) && isStr(e.updatedAt) && isStr(e.cliVersion);
22608
22665
  };
22609
22666
  var readLock = async (file3, scope) => {
22610
22667
  let text;
@@ -22958,7 +23015,7 @@ var knownSecrets = async (ctx, p) => {
22958
23015
  var install = async (ctx, p) => {
22959
23016
  const backup = new Backup(ctx);
22960
23017
  const created = [];
22961
- const res = { items: [], notes: [], touched: /* @__PURE__ */ new Set(), cliInstalled: false };
23018
+ const res = { items: [], notes: [], touched: /* @__PURE__ */ new Set(), cliInstalled: false, requiredEnvRefs: /* @__PURE__ */ new Set() };
22962
23019
  const prev = p.previous?.items ?? [];
22963
23020
  const owned = new Set(prev.flatMap((i) => i.kind === "skill" ? [lockItemTarget(ctx, p.scope, p.root, i)] : []).filter((x) => Boolean(x)));
22964
23021
  let rolledBack = null;
@@ -23066,6 +23123,7 @@ var installMcp = async (ctx, p, action, values, backup, prev, res) => {
23066
23123
  if (p.scope === "project" && action.agent === "codex") res.notes.push(tr(ctx, "Codex \u0447\u0438\u0442\u0430\u0435\u0442 .codex/config.toml \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0434\u043E\u0432\u0435\u0440\u0435\u043D\u043D\u044B\u0445 \u043F\u0440\u043E\u0435\u043A\u0442\u0430\u0445.", "Codex reads .codex/config.toml only in trusted projects."));
23067
23124
  if (p.scope === "project" && action.agent === "amp") res.notes.push(tr(ctx, "Amp: \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 amp mcp approve.", "Amp: approve the server with amp mcp approve."));
23068
23125
  if (r.references.length) res.notes.push(tr(ctx, `\u0417\u0430\u0434\u0430\u0439\u0442\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043F\u0435\u0440\u0435\u0434 \u0437\u0430\u043F\u0443\u0441\u043A\u043E\u043C \u0430\u0433\u0435\u043D\u0442\u0430: ${r.references.join(", ")}`, `Set environment variables before starting the agent: ${r.references.join(", ")}`));
23126
+ for (const name2 of r.references) if (r.component.env.find((e) => e.name === name2)?.required) res.requiredEnvRefs.add(name2);
23069
23127
  if (r.secrets.length) res.notes.push(tr(ctx, `${title}: \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0441\u0435\u043A\u0440\u0435\u0442\u043E\u0432 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u044B \u0432 ${file3}.`, `${title}: secret values are stored in ${file3}.`));
23070
23128
  };
23071
23129
  var installCli = async (ctx, p, action, res) => {
@@ -23146,6 +23204,21 @@ var findInstalled = async (ctx, input2, flags) => {
23146
23204
  };
23147
23205
 
23148
23206
  // src/commands/add.ts
23207
+ var envHint = (ctx, names) => {
23208
+ const list = names.join(", ");
23209
+ if (ctx.platform === "darwin") {
23210
+ return tr(
23211
+ ctx,
23212
+ `\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u043D\u044B \u0430\u0433\u0435\u043D\u0442\u0443 \u043F\u0440\u0438 \u0437\u0430\u043F\u0443\u0441\u043A\u0435: ${list}. \u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435, \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0435 \u0438\u0437 Dock \u0438\u043B\u0438 Launchpad, \u043D\u0435 \u0432\u0438\u0434\u0438\u0442 \u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0438\u0437 ~/.zshrc. \u0417\u0430\u0434\u0430\u0439\u0442\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 launchctl setenv \u0418\u041C\u042F \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 (\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 \u0434\u043E \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438) \u0438\u043B\u0438 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439\u0442\u0435 \u0430\u0433\u0435\u043D\u0442\u0430 \u0438\u0437 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430, \u0433\u0434\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u0443\u0436\u0435 \u0437\u0430\u0434\u0430\u043D\u0430, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 open -a Cursor \u0438\u0437 \u044D\u0442\u043E\u0433\u043E \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430.`,
23213
+ `The agent needs these environment variables at launch: ${list}. An app opened from the Dock or Launchpad does not see exports from ~/.zshrc. Set the value with launchctl setenv NAME value (lasts until reboot), or start the agent from a terminal where the variable is already set, for example open -a Cursor from that terminal.`
23214
+ );
23215
+ }
23216
+ return tr(
23217
+ ctx,
23218
+ `\u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u043D\u044B \u0430\u0433\u0435\u043D\u0442\u0443 \u043F\u0440\u0438 \u0437\u0430\u043F\u0443\u0441\u043A\u0435: ${list}. \u0417\u0430\u0434\u0430\u0439\u0442\u0435 \u0438\u0445 \u0432 \u043F\u0440\u043E\u0444\u0438\u043B\u0435 \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0438 \u0438\u043B\u0438 \u0432 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0445 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u0430\u0433\u0435\u043D\u0442\u0430.`,
23219
+ `The agent needs these environment variables at launch: ${list}. Set them in your shell profile or in the agent's own environment settings.`
23220
+ );
23221
+ };
23149
23222
  var runAdd = async (ctx, args, flags) => {
23150
23223
  if (args.length !== 1) throw new CliError(64, tr(ctx, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043E\u0434\u043D\u0443 \u0437\u0430\u043F\u0438\u0441\u044C: npx skillfoxx add <\u0440\u0430\u0437\u0434\u0435\u043B>/<slug>", "Name one entry: npx skillfoxx add <section>/<slug>"));
23151
23224
  const { section, slug } = await resolveTarget(ctx, args[0]);
@@ -23155,7 +23228,7 @@ var runAdd = async (ctx, args, flags) => {
23155
23228
  if (!g.ok) throw new CliError(g.code, g.message);
23156
23229
  const b = body;
23157
23230
  const recipe = b.recipe;
23158
- for (const w of [...g.warnings, ...await checkRuntimes(ctx, recipe)]) ctx.err(w);
23231
+ for (const w of [...g.warnings, ...await checkRuntimes(ctx, recipe), ...remoteAuthWarnings(ctx, recipe)]) ctx.err(w);
23159
23232
  await riskConsent(ctx, g, Boolean(flags.yes));
23160
23233
  const scope = await pickScope(ctx, flags);
23161
23234
  const root = await rootFor(ctx, scope);
@@ -23186,12 +23259,15 @@ var runAdd = async (ctx, args, flags) => {
23186
23259
  scope,
23187
23260
  agents: [.../* @__PURE__ */ new Set([...previous?.agents ?? [], ...result.touched])],
23188
23261
  items: mergeItems(previous?.items ?? [], result.items),
23262
+ vars: recipeVars(recipe),
23189
23263
  installedAt: previous?.installedAt ?? now,
23190
23264
  updatedAt: now,
23191
23265
  cliVersion: VERSION
23192
23266
  };
23193
23267
  await writeLock(lockFile, lock);
23194
23268
  for (const note of new Set(result.notes)) ctx.out(note);
23269
+ const missingEnv = [...result.requiredEnvRefs].filter((n) => !ctx.env[n]);
23270
+ if (missingEnv.length) ctx.out(envHint(ctx, missingEnv));
23195
23271
  await sendEvents(ctx, installEvents(key, result, previous ? "update" : "add"));
23196
23272
  ctx.out(tr(ctx, `\u0413\u043E\u0442\u043E\u0432\u043E: ${key}`, `Done: ${key}`));
23197
23273
  return 0;
@@ -23199,6 +23275,8 @@ var runAdd = async (ctx, args, flags) => {
23199
23275
 
23200
23276
  // src/commands/doctor.ts
23201
23277
  import { lstat as lstat3, stat as stat5 } from "node:fs/promises";
23278
+ var envRows = (ctx, key, scope, vars) => (vars ?? []).map((v) => ({ key, scope, name: v.name, required: v.required, secret: v.secret, status: ctx.env[v.name] ? "ok" : "missing" }));
23279
+ var envProblems = (ctx, rows) => rows.filter((r) => r.required && r.status === "missing").sort((a, b) => Number(b.secret) - Number(a.secret)).map((r) => ({ key: r.key, scope: r.scope, kind: "env", detail: r.secret ? tr(ctx, `${r.name} (\u0441\u0435\u043A\u0440\u0435\u0442)`, `${r.name} (secret)`) : r.name }));
23202
23280
  var localProblems = async (ctx, key, scope, entry, root) => {
23203
23281
  const out = [];
23204
23282
  for (const item of entry.items) {
@@ -23238,15 +23316,20 @@ var LABEL = {
23238
23316
  blocked: ["\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u0430", "installation is blocked"],
23239
23317
  unsupported: ["\u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0430\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F", "automatic install is no longer supported"],
23240
23318
  risk: ["\u0440\u0438\u0441\u043A \u0432\u044B\u0440\u043E\u0441 \u0434\u043E high", "risk went up to high"],
23241
- unchecked: ["\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C", "could not check"]
23319
+ unchecked: ["\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C", "could not check"],
23320
+ env: ["\u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0435 \u0437\u0430\u0434\u0430\u043D\u0430", "environment variable is not set"]
23242
23321
  };
23243
23322
  var runDoctor = async (ctx, _args, flags) => {
23244
23323
  const problems = [];
23324
+ const env = [];
23245
23325
  let checked = 0;
23246
23326
  for (const s of await lockScopes(ctx, flags)) {
23247
23327
  for (const [key, entry] of Object.entries(s.lock.entries)) {
23248
23328
  checked++;
23249
23329
  problems.push(...await localProblems(ctx, key, s.scope, entry, s.root));
23330
+ const rows = envRows(ctx, key, s.scope, entry.vars);
23331
+ env.push(...rows);
23332
+ problems.push(...envProblems(ctx, rows));
23250
23333
  try {
23251
23334
  const body = await fetchRecipe(ctx, entry.section, entry.slug);
23252
23335
  const g = gate(ctx, body);
@@ -23263,9 +23346,13 @@ var runDoctor = async (ctx, _args, flags) => {
23263
23346
  }
23264
23347
  }
23265
23348
  }
23266
- if (flags.json) printJson(ctx, { checked, problems });
23267
- else if (!problems.length) ctx.out(tr(ctx, `\u041F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0437\u0430\u043F\u0438\u0441\u0435\u0439: ${checked}, \u043F\u0440\u043E\u0431\u043B\u0435\u043C \u043D\u0435\u0442.`, `Entries checked: ${checked}, no problems.`));
23268
- else for (const p of problems) ctx.out(`${p.key}: ${tr(ctx, ...LABEL[p.kind])}, ${p.detail}`);
23349
+ const optionalMissing = env.filter((r) => !r.required && r.status === "missing");
23350
+ if (flags.json) printJson(ctx, { checked, problems, env });
23351
+ else if (!problems.length && !optionalMissing.length) ctx.out(tr(ctx, `\u041F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0437\u0430\u043F\u0438\u0441\u0435\u0439: ${checked}, \u043F\u0440\u043E\u0431\u043B\u0435\u043C \u043D\u0435\u0442.`, `Entries checked: ${checked}, no problems.`));
23352
+ else {
23353
+ for (const p of problems) ctx.out(`${p.key}: ${tr(ctx, ...LABEL[p.kind])}, ${p.detail}`);
23354
+ for (const r of optionalMissing) ctx.out(tr(ctx, `${r.key}: \u043D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u043D\u0435 \u0437\u0430\u0434\u0430\u043D\u0430, ${r.name}`, `${r.key}: optional variable is not set, ${r.name}`));
23355
+ }
23269
23356
  return problems.length ? 2 : 0;
23270
23357
  };
23271
23358
 
@@ -23465,6 +23552,7 @@ var runUpdate = async (ctx, args, flags) => {
23465
23552
  risk: body.risk.level ?? null,
23466
23553
  agents: [...result.touched],
23467
23554
  items: result.items,
23555
+ vars: recipeVars(recipe),
23468
23556
  updatedAt: ctx.now().toISOString(),
23469
23557
  cliVersion: VERSION
23470
23558
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillfoxx",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Установка скиллов, MCP-серверов и CLI из каталога SkillFoxx в Claude Code, Cursor, Codex и другие агенты",
5
5
  "type": "module",
6
6
  "bin": { "skillfoxx": "dist/cli.js" },