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 +2 -1
- package/README.md +2 -1
- package/dist/cli.js +99 -11
- package/package.json +1 -1
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
|
|
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.
|
|
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
|
-
|
|
23267
|
-
|
|
23268
|
-
else
|
|
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