@logic-joe/fleek 0.2.2 → 0.2.3

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/dist/fleek.mjs CHANGED
@@ -1213,6 +1213,7 @@ var require_error_codes = __commonJS({
1213
1213
  "not_string",
1214
1214
  "path",
1215
1215
  "path_import",
1216
+ "plugin_commit",
1216
1217
  "plugin_name",
1217
1218
  "plugin_source",
1218
1219
  "plugin_version",
@@ -1279,7 +1280,8 @@ var require_error_codes = __commonJS({
1279
1280
  "plugins",
1280
1281
  "marketplace",
1281
1282
  "source",
1282
- "version"
1283
+ "version",
1284
+ "commit"
1283
1285
  ];
1284
1286
  exports.KIT_ISSUE_TARGETS = ["claude", "codex", "cursor", "orca", "generic"];
1285
1287
  exports.KIT_CONFLICT_REASONS = ["targets_changed", "kit_version_changed", "reattest_changed", "example_key_taken", "signing_key_missing"];
@@ -4585,7 +4587,7 @@ var require_setup_session = __commonJS({
4585
4587
  exports.SETUP_SESSION_TTL_MINUTES = 30;
4586
4588
  exports.SETUP_SESSION_OPEN_MAX = 10;
4587
4589
  exports.SETUP_SESSION_CLAIM_LIMIT = 10;
4588
- exports.FLEEK_CLI_SETUP_VERSION_DEFAULT = "0.2.2";
4590
+ exports.FLEEK_CLI_SETUP_VERSION_DEFAULT = "0.2.3";
4589
4591
  exports.FLEEK_CLI_PACKAGE = "@logic-joe/fleek";
4590
4592
  exports.FLEEK_CANONICAL_SERVER = "https://api.onfleek.app";
4591
4593
  exports.SETUP_CLIENT_APPS = ["claude-code", "codex", "orca", "cursor", "other"];
@@ -35609,7 +35611,7 @@ var import_types8 = __toESM(require_dist(), 1);
35609
35611
  import { spawn as spawn9 } from "node:child_process";
35610
35612
  import { createHash as createHash9, randomBytes as randomBytes12 } from "node:crypto";
35611
35613
  import { realpath as realpath19, rm as rm13 } from "node:fs/promises";
35612
- import { homedir as homedir8, hostname as osHostname2 } from "node:os";
35614
+ import { hostname as osHostname2 } from "node:os";
35613
35615
  import { join as join33, sep as sep10 } from "node:path";
35614
35616
  import { parseArgs } from "node:util";
35615
35617
 
@@ -35672,7 +35674,13 @@ function kitRefusalCategory(code, reason) {
35672
35674
  }
35673
35675
 
35674
35676
  // src/config.ts
35675
- function defaultConfigDir(env = process.env, platform = process.platform, home = (platform === "win32" ? env.USERPROFILE ?? env.HOME : env.HOME) ?? homedir()) {
35677
+ function userHome(env = process.env, platform = process.platform) {
35678
+ return (platform === "win32" ? env.USERPROFILE ?? env.HOME : env.HOME) ?? homedir();
35679
+ }
35680
+ function contextHome(context) {
35681
+ return context.home ?? context.deps.home ?? userHome(context.env);
35682
+ }
35683
+ function defaultConfigDir(env = process.env, platform = process.platform, home = userHome(env, platform)) {
35676
35684
  if (env.FLEEK_CONFIG_DIR) return env.FLEEK_CONFIG_DIR;
35677
35685
  if (platform === "win32") return join(env.APPDATA ?? join(home, "AppData", "Roaming"), "fleek");
35678
35686
  if (platform === "darwin") return join(home, "Library", "Application Support", "fleek");
@@ -36375,7 +36383,7 @@ var EN = {
36375
36383
  "uninstall.offline": "Continue with local removal only? Remote access remains until revoked in the web UI. [y/N]",
36376
36384
  "service.removeTitle": "Remove Fleek review service",
36377
36385
  "service.removeConfirm": "Revoke the service sign-in and remove these service-host items? [y/N]",
36378
- "service.removeOffline": "Revocation unavailable. Update the host with fleek service setup or revoke all sign-ins of the service account at https://app.onfleek.app/admin/bots. Continue locally? [y/N]",
36386
+ "service.removeOffline": "Revocation unavailable. Update the host with fleek service setup or ask a workspace admin to sign out this host under the service account’s sign-ins at https://app.onfleek.app/admin/bots. Continue locally? [y/N]",
36379
36387
  "service.removeDone": "Review service removed",
36380
36388
  "help.description.service.remove": "Revoke and remove the review service host",
36381
36389
  "help.service.remove": "fleek service remove [review] [--dry-run] [--local-only]",
@@ -36793,6 +36801,16 @@ var EN = {
36793
36801
  "setup.plugins.ask": "Install {plugin} in {client}? [y/N/a=all]",
36794
36802
  "setup.plugins.restart": "Restart the client so it loads the plugin.",
36795
36803
  "setup.plugins.auto": "Approved with the signed kit: installed without asking (service account).",
36804
+ "setup.plugins.otherCommit": "commit {commit} (approved: {approved})",
36805
+ "setup.plugins.bound": "Bound to commit {commit}: installed only when the source serves exactly this commit.",
36806
+ "setup.plugins.unbound": "Not bound to a commit: you install what the source serves today.",
36807
+ "setup.plugins.unboundShort": "not bound",
36808
+ "setup.plugins.unboundAuto": "{plugin}: not bound to a commit - not installed on a service account (the admin binds it in the kit).",
36809
+ "setup.plugins.upstreamMoved": "the source no longer serves the approved commit (the admin approves a new kit version)",
36810
+ "setup.plugins.upstreamUnchecked": "the source commit could not be checked (git missing or source unreachable)",
36811
+ "setup.plugins.checkoutOther": "the client's marketplace checkout is not at the approved commit (nothing installed)",
36812
+ "setup.plugins.contentUnproven": "content not proven (the plugin is not inside the marketplace checkout at the approved commit)",
36813
+ "setup.plugins.notLocal": "the plugin is fetched from a source outside the approved marketplace commit (not supported for bound plugins; nothing installed)",
36796
36814
  "setup.reason.http": "HTTP {status}",
36797
36815
  "setup.action.install": "install",
36798
36816
  "setup.action.update": "update",
@@ -37010,7 +37028,7 @@ var DE = {
37010
37028
  "uninstall.offline": "Nur lokal fortfahren? Der Serverzugang bleibt bis zum Widerruf in der Weboberfläche bestehen. [j/N]",
37011
37029
  "service.removeTitle": "Fleek-Review-Dienst entfernen",
37012
37030
  "service.removeConfirm": "Dienstanmeldung widerrufen und diese Diensthost-Einträge entfernen? [j/N]",
37013
- "service.removeOffline": "Widerruf nicht möglich. Host mit fleek service setup aktualisieren oder alle Anmeldungen des Dienstkontos unter https://app.onfleek.app/admin/bots widerrufen. Lokal fortfahren? [j/N]",
37031
+ "service.removeOffline": "Widerruf nicht möglich. Host mit fleek service setup aktualisieren oder einen Workspace-Admin bitten, diesen Host unter den Anmeldungen des Dienstkontos auf https://app.onfleek.app/admin/bots abzumelden. Lokal fortfahren? [j/N]",
37014
37032
  "service.removeDone": "Review-Dienst entfernt",
37015
37033
  "help.description.service.remove": "Review-Diensthost widerrufen und entfernen",
37016
37034
  "help.service.remove": "fleek service remove [review] [--dry-run] [--local-only]",
@@ -37428,6 +37446,16 @@ var DE = {
37428
37446
  "setup.plugins.ask": "{plugin} in {client} installieren? [j/N/a=alle]",
37429
37447
  "setup.plugins.restart": "Client neu starten, damit er das Plugin lädt.",
37430
37448
  "setup.plugins.auto": "Mit dem signierten Kit freigegeben: wird ohne Rückfrage installiert (Service-Konto).",
37449
+ "setup.plugins.otherCommit": "Commit {commit} (freigegeben: {approved})",
37450
+ "setup.plugins.bound": "An Commit {commit} gebunden: wird nur installiert, wenn die Quelle genau diesen Commit liefert.",
37451
+ "setup.plugins.unbound": "Nicht an einen Commit gebunden: du installierst, was die Quelle heute liefert.",
37452
+ "setup.plugins.unboundShort": "nicht gebunden",
37453
+ "setup.plugins.unboundAuto": "{plugin}: nicht an einen Commit gebunden – auf einem Service-Konto nicht installiert (der Admin bindet es im Kit).",
37454
+ "setup.plugins.upstreamMoved": "die Quelle liefert nicht mehr den freigegebenen Commit (der Admin gibt eine neue Kit-Version frei)",
37455
+ "setup.plugins.upstreamUnchecked": "der Commit der Quelle ließ sich nicht prüfen (git fehlt oder Quelle nicht erreichbar)",
37456
+ "setup.plugins.checkoutOther": "der Marketplace-Checkout des Clients steht nicht auf dem freigegebenen Commit (nichts installiert)",
37457
+ "setup.plugins.contentUnproven": "Inhalt nicht bestätigt (das Plugin liegt nicht im Marketplace-Checkout des freigegebenen Commits)",
37458
+ "setup.plugins.notLocal": "das Plugin kommt aus einer Quelle außerhalb des freigegebenen Marketplace-Commits (bei gebundenen Plugins nicht unterstützt; nichts installiert)",
37431
37459
  "setup.reason.http": "HTTP {status}",
37432
37460
  "setup.action.install": "installieren",
37433
37461
  "setup.action.update": "aktualisieren",
@@ -39589,6 +39617,7 @@ function isSkillName(name) {
39589
39617
  var MCP_SERVER_NAME = /^[a-z][a-z0-9-]{1,32}$/;
39590
39618
  var PLUGIN_NAME = /^[a-z][a-z0-9-]{1,63}$/;
39591
39619
  var PLUGIN_VERSION = /^[0-9A-Za-z][0-9A-Za-z.+-]{0,31}$/;
39620
+ var PLUGIN_COMMIT = /^[0-9a-f]{40}$/;
39592
39621
  var PLUGIN_SOURCE = /^https:\/\/github\.com\/[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\/[A-Za-z0-9._-]{1,100}$/;
39593
39622
  var BASE_PROTOCOL = /^fleek-work-protocol\/(0|[1-9][0-9]{0,3})\.(0|[1-9][0-9]{0,3})$/;
39594
39623
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
@@ -39596,6 +39625,7 @@ var RESERVED_MCP_NAME = "fleek";
39596
39625
  var CONTENT_KEYS = ["format", "skills", "agentsBlock", "docRefs", "mcpServers", "base"];
39597
39626
  var OPTIONAL_CONTENT_KEYS = ["plugins"];
39598
39627
  var PLUGIN_KEYS = ["name", "marketplace", "source", "version"];
39628
+ var PLUGIN_OPTIONAL_KEYS = ["commit"];
39599
39629
  var SKILL_KEYS = ["name", "description", "body"];
39600
39630
  var DOC_REF_KEYS = ["documentId", "title"];
39601
39631
  var MCP_SERVER_KEYS = ["name", "url"];
@@ -46982,7 +47012,7 @@ function checkPlugins(value, errors, warnings) {
46982
47012
  errors.push({ reason: "not_object", path });
46983
47013
  return;
46984
47014
  }
46985
- closedKeys(entry, PLUGIN_KEYS, path, errors);
47015
+ closedKeys(entry, PLUGIN_KEYS, path, errors, [], PLUGIN_OPTIONAL_KEYS);
46986
47016
  for (const field of PLUGIN_KEYS) {
46987
47017
  const text6 = entry[field];
46988
47018
  if (typeof text6 !== "string") {
@@ -47004,6 +47034,8 @@ function checkPlugins(value, errors, warnings) {
47004
47034
  }
47005
47035
  if (typeof version2 !== "string" || !PLUGIN_VERSION.test(version2))
47006
47036
  errors.push({ reason: "plugin_version", path: `${path}.version` });
47037
+ if ("commit" in entry && (typeof entry.commit !== "string" || !PLUGIN_COMMIT.test(entry.commit)))
47038
+ errors.push({ reason: "plugin_commit", path: `${path}.commit` });
47007
47039
  if (typeof name === "string" && typeof marketplace === "string") {
47008
47040
  const id = `${name}@${marketplace}`;
47009
47041
  if (ids.has(id))
@@ -48829,7 +48861,7 @@ async function orcaPick(deps, values) {
48829
48861
  }
48830
48862
 
48831
48863
  // src/uninstall.ts
48832
- import { cp as cp2, lstat as lstat11, mkdir as mkdir5, readFile as readFile15, readdir as readdir10, realpath as realpath12, rm as rm8, rmdir as rmdir2 } from "node:fs/promises";
48864
+ import { cp as cp2, lstat as lstat11, mkdir as mkdir5, readFile as readFile16, readdir as readdir10, realpath as realpath12, rm as rm8, rmdir as rmdir2 } from "node:fs/promises";
48833
48865
  import { dirname as dirname14, join as join22, sep as sep6 } from "node:path";
48834
48866
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
48835
48867
  import { randomBytes as randomBytes9 } from "node:crypto";
@@ -51039,7 +51071,7 @@ async function deletedCheckout(root) {
51039
51071
  } catch (error) {
51040
51072
  if (error.code !== "ENOENT") return false;
51041
51073
  }
51042
- if (/^\/(?:Volumes|media|mnt)(?:\/|$)/.test(root)) return false;
51074
+ if (/^\/(?:Volumes|media|mnt|run\/media)(?:\/|$)/.test(root)) return false;
51043
51075
  for (let parent = dirname12(root); ; parent = dirname12(parent)) {
51044
51076
  try {
51045
51077
  const info = await lstat8(parent);
@@ -51322,11 +51354,12 @@ async function planCodexSkillRemovals(configDir, profileKey2) {
51322
51354
  }
51323
51355
 
51324
51356
  // src/plugin-step.ts
51325
- import { lstat as lstat10, readdir as readdir9, realpath as realpath11, stat as stat11 } from "node:fs/promises";
51357
+ import { lstat as lstat10, readdir as readdir9, readFile as readFile15, realpath as realpath11, stat as stat11 } from "node:fs/promises";
51326
51358
  import { join as join21, sep as sep5 } from "node:path";
51327
51359
  var PLUGIN_CLIENTS = ["claude", "codex"];
51328
51360
  var LABEL3 = { claude: "Claude Code", codex: "Codex" };
51329
51361
  var LIST_TIMEOUT_MS = 2e4;
51362
+ var STATE_READ_LIMIT = 4 * 1024 * 1024;
51330
51363
  var INSTALL_TIMEOUT_MS = 18e4;
51331
51364
  var pluginId = (plugin) => `${plugin.name}@${plugin.marketplace}`;
51332
51365
  async function generationPlugins(domain, gen) {
@@ -51342,9 +51375,10 @@ async function generationPlugins(domain, gen) {
51342
51375
  }
51343
51376
  for (const raw of content3.plugins ?? []) {
51344
51377
  const plugin = raw;
51345
- if (!plugin || typeof plugin.name !== "string" || typeof plugin.marketplace !== "string" || typeof plugin.source !== "string" || typeof plugin.version !== "string" || !PLUGIN_NAME.test(plugin.name) || !PLUGIN_NAME.test(plugin.marketplace) || !PLUGIN_SOURCE.test(plugin.source) || !PLUGIN_VERSION.test(plugin.version)) continue;
51346
- const id = `${plugin.name}@${plugin.marketplace} ${plugin.source} ${plugin.version}`;
51347
- const entry = byId.get(id) ?? { plugin: { name: plugin.name, marketplace: plugin.marketplace, source: plugin.source, version: plugin.version }, kits: [] };
51378
+ if (!plugin || typeof plugin.name !== "string" || typeof plugin.marketplace !== "string" || typeof plugin.source !== "string" || typeof plugin.version !== "string" || !PLUGIN_NAME.test(plugin.name) || !PLUGIN_NAME.test(plugin.marketplace) || !PLUGIN_SOURCE.test(plugin.source) || !PLUGIN_VERSION.test(plugin.version) || plugin.commit !== void 0 && (typeof plugin.commit !== "string" || !PLUGIN_COMMIT.test(plugin.commit))) continue;
51379
+ const commit = typeof plugin.commit === "string" ? plugin.commit : void 0;
51380
+ const id = `${plugin.name}@${plugin.marketplace} ${plugin.source} ${plugin.version} ${commit ?? "-"}`;
51381
+ const entry = byId.get(id) ?? { plugin: { name: plugin.name, marketplace: plugin.marketplace, source: plugin.source, version: plugin.version, ...commit ? { commit } : {} }, kits: [] };
51348
51382
  entry.kits.push(kit2.kitKey);
51349
51383
  byId.set(id, entry);
51350
51384
  }
@@ -51382,10 +51416,89 @@ async function marketplaces(system, home, binary) {
51382
51416
  const from = entry.marketplaceSource;
51383
51417
  source = from?.sourceType === "git" ? canonicalGithub(from.source) : null;
51384
51418
  }
51385
- out.set(entry.name, source);
51419
+ const location = home.client === "codex" ? entry.root : entry.installLocation;
51420
+ const root = typeof location === "string" && location.startsWith("/") ? location : null;
51421
+ out.set(entry.name, { source, root });
51386
51422
  }
51387
51423
  return out;
51388
51424
  }
51425
+ async function readSmall(path) {
51426
+ try {
51427
+ const info = await lstat10(path);
51428
+ if (!info.isFile() || info.size > STATE_READ_LIMIT) return null;
51429
+ return await readFile15(path, "utf8");
51430
+ } catch {
51431
+ return null;
51432
+ }
51433
+ }
51434
+ async function claudeInstalledCommit(system, home, id, installPath) {
51435
+ const text6 = await readSmall(join21(home.configDir ?? join21(system.home, ".claude"), "plugins", "installed_plugins.json"));
51436
+ if (text6 === null) return null;
51437
+ let parsed;
51438
+ try {
51439
+ parsed = JSON.parse(text6);
51440
+ } catch {
51441
+ return null;
51442
+ }
51443
+ const entries = parsed?.plugins?.[id];
51444
+ if (!Array.isArray(entries)) return null;
51445
+ const entry = entries.find((item2) => !!item2 && typeof item2 === "object" && item2.scope === "user" && (installPath === null || item2.installPath === installPath));
51446
+ return typeof entry?.gitCommitSha === "string" && PLUGIN_COMMIT.test(entry.gitCommitSha) ? entry.gitCommitSha : null;
51447
+ }
51448
+ async function checkoutCommit(root) {
51449
+ if (!root) return null;
51450
+ const git2 = join21(root, ".git");
51451
+ const head = (await readSmall(join21(git2, "HEAD")))?.trim() ?? "";
51452
+ if (PLUGIN_COMMIT.test(head)) return head;
51453
+ const ref = /^ref: (refs\/heads\/[A-Za-z0-9._/-]{1,200})$/.exec(head)?.[1];
51454
+ if (!ref || ref.includes("..")) return null;
51455
+ const loose = (await readSmall(join21(git2, ref)))?.trim() ?? "";
51456
+ if (PLUGIN_COMMIT.test(loose)) return loose;
51457
+ for (const line of (await readSmall(join21(git2, "packed-refs")) ?? "").split("\n")) {
51458
+ const [sha, name] = line.trim().split(" ");
51459
+ if (name === ref && sha && PLUGIN_COMMIT.test(sha)) return sha;
51460
+ }
51461
+ return null;
51462
+ }
51463
+ var MANIFESTS = {
51464
+ claude: [".claude-plugin/marketplace.json"],
51465
+ codex: [".agents/plugins/marketplace.json", ".agents/plugins/api_marketplace.json", ".claude-plugin/marketplace.json", ".cursor-plugin/marketplace.json"]
51466
+ };
51467
+ async function localPluginSource(root, client, name) {
51468
+ let text6 = null;
51469
+ for (const file of MANIFESTS[client]) {
51470
+ const present = await lstat10(join21(root, file)).then(() => true, () => false);
51471
+ if (!present) continue;
51472
+ text6 = await readSmall(join21(root, file));
51473
+ break;
51474
+ }
51475
+ if (text6 === null) return false;
51476
+ let manifest;
51477
+ try {
51478
+ manifest = JSON.parse(text6);
51479
+ } catch {
51480
+ return false;
51481
+ }
51482
+ if (!manifest || !Array.isArray(manifest.plugins) || client === "claude" && manifest.metadata?.pluginRoot !== void 0) return false;
51483
+ const entries = manifest.plugins.filter((item2) => !!item2 && typeof item2 === "object" && item2.name === name);
51484
+ if (!entries.length) return false;
51485
+ const realRoot = await realpath11(root).catch(() => null);
51486
+ if (!realRoot) return false;
51487
+ for (const entry of entries) {
51488
+ const raw = entry.source;
51489
+ const path = typeof raw === "string" ? raw : client === "codex" && raw && typeof raw === "object" && raw.source === "local" ? raw.path : null;
51490
+ if (typeof path !== "string") return false;
51491
+ const relative3 = path === "." || path === "./" ? "" : path.startsWith("./") ? path.slice(2).replace(/\/$/, "") : null;
51492
+ if (relative3 === null || relative3.includes("\\") || relative3 && relative3.split("/").some((part) => !part || part === "." || part === "..")) return false;
51493
+ const real = await realpath11(join21(root, relative3)).catch(() => null);
51494
+ if (!real || real !== realRoot && !real.startsWith(`${realRoot}${sep5}`) || !await stat11(real).then((info) => info.isDirectory(), () => false)) return false;
51495
+ }
51496
+ return true;
51497
+ }
51498
+ async function checkoutVerdict(client, root, plugin) {
51499
+ if (!root || await checkoutCommit(root) !== plugin.commit) return "other_commit";
51500
+ return await localPluginSource(root, client, plugin.name) ? "ok" : "not_local";
51501
+ }
51389
51502
  async function pluginHomes(system, clients, options = {}) {
51390
51503
  const homes = [];
51391
51504
  const seen = /* @__PURE__ */ new Set();
@@ -51450,29 +51563,47 @@ async function inspectPlugins(system, clients, plugins, options = {}) {
51450
51563
  const base = { client: home.client, configDir: home.configDir, account: home.account };
51451
51564
  for (const plugin of plugins) {
51452
51565
  if (!installed || !markets) {
51453
- rows.push({ ...base, plugin, state: "unchecked", installed: null, installPath: null });
51566
+ rows.push({ ...base, plugin, state: "unchecked", installed: null, installPath: null, installedCommit: null, marketAdded: false });
51454
51567
  continue;
51455
51568
  }
51456
51569
  const found = installed.find((entry) => entry.id === pluginId(plugin));
51457
- const of = (state) => ({ ...base, plugin, state, installed: found?.version ?? null, installPath: found?.installPath ?? null });
51458
51570
  const market = markets.get(plugin.marketplace);
51459
- const sourceOk = market !== void 0 && market === canonicalGithub(plugin.source);
51571
+ const sourceOk = market !== void 0 && market.source === canonicalGithub(plugin.source);
51572
+ const installedCommit = plugin.commit && found && sourceOk ? home.client === "claude" ? await claudeInstalledCommit(system, home, found.id, found.installPath) : await checkoutCommit(market.root) : null;
51573
+ const verdict = plugin.commit && found && sourceOk && installedCommit === plugin.commit ? await checkoutVerdict(home.client, market.root, plugin) : null;
51574
+ const of = (state) => ({ ...base, plugin, state, installed: found?.version ?? null, installPath: found?.installPath ?? null, installedCommit, marketAdded: sourceOk });
51460
51575
  if (plugin.conflict) rows.push(of("unverified"));
51461
51576
  else if (!found) rows.push(of(market !== void 0 && !sourceOk ? "unverified" : "missing"));
51462
51577
  else if (!sourceOk) rows.push(of("unverified"));
51463
51578
  else if (found.errors) rows.push(of("error"));
51579
+ else if (plugin.commit && installedCommit !== plugin.commit) rows.push(of("other_commit"));
51580
+ else if (verdict !== null && verdict !== "ok") rows.push(of("unverified"));
51464
51581
  else if (!found.enabled) rows.push(of("disabled"));
51465
51582
  else rows.push(of(found.version === plugin.version ? "current" : "other_version"));
51466
51583
  }
51467
51584
  }
51468
51585
  return rows;
51469
51586
  }
51470
- var pluginActionNeeded = (rows) => rows.some((row) => row.state !== "current" && row.state !== "other_version");
51587
+ var pluginActionNeeded = (rows) => rows.some((row) => row.state !== "current" && row.state !== "other_version" && row.state !== "other_commit");
51471
51588
  function pluginCommands(row) {
51472
51589
  const id = pluginId(row.plugin);
51473
51590
  if (row.state === "disabled") return row.client === "claude" ? [["plugin", "enable", id]] : [];
51474
51591
  if (row.state !== "missing") return [];
51475
- return row.client === "claude" ? [["plugin", "install", row.plugin.name, "--marketplace", row.plugin.source, "--scope", "user"]] : [["plugin", "marketplace", "add", row.plugin.source], ["plugin", "add", id]];
51592
+ if (row.client === "claude" && row.plugin.commit) return [...row.marketAdded ? [] : [["plugin", "marketplace", "add", row.plugin.source]], ["plugin", "install", id, "--scope", "user"]];
51593
+ return row.client === "claude" ? [["plugin", "install", row.plugin.name, "--marketplace", row.plugin.source, "--scope", "user"]] : [["plugin", "marketplace", "add", row.plugin.source, ...row.plugin.commit ? ["--ref", row.plugin.commit] : []], ["plugin", "add", id]];
51594
+ }
51595
+ var shortCommit = (commit) => commit ? commit.slice(0, 12) : "?";
51596
+ async function upstreamServes(system, plugin) {
51597
+ const git2 = await system.which("git");
51598
+ if (!git2) return "unchecked";
51599
+ const result = await system.exec(git2, ["ls-remote", "--", `${plugin.source}.git`, "HEAD"], {
51600
+ cwd: system.home,
51601
+ timeoutMs: LIST_TIMEOUT_MS,
51602
+ env: { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: void 0, SSH_ASKPASS: void 0 }
51603
+ });
51604
+ if (result.code !== 0 || result.timedOut) return "unchecked";
51605
+ const head = /^([0-9a-f]{40})\tHEAD$/m.exec(result.stdout)?.[1];
51606
+ return !head ? "unchecked" : head === plugin.commit ? "yes" : "moved";
51476
51607
  }
51477
51608
  var shown2 = (row, binary, args, env) => `${row.configDir ? `CLAUDE_CONFIG_DIR=${shellWord(row.configDir)} ` : row.client === "claude" && env.CLAUDE_CONFIG_DIR ? "env -u CLAUDE_CONFIG_DIR " : ""}${[binary, ...args].map(shellWord).join(" ")}`;
51478
51609
  var rowLabel = (row) => row.account ? `${LABEL3[row.client]} · Orca ${row.account}` : LABEL3[row.client];
@@ -51480,9 +51611,10 @@ function stateText(ctx, row) {
51480
51611
  const s = ctx.out.style;
51481
51612
  if (row.state === "current") return s.state("ok", ctx.t("setup.plugins.current", { version: neutralizeLine(row.plugin.version) }));
51482
51613
  if (row.state === "other_version") return s.state("outdated", ctx.t("setup.plugins.otherVersion", { version: neutralizeLine(row.installed ?? "?"), approved: neutralizeLine(row.plugin.version) }));
51614
+ if (row.state === "other_commit") return s.state("outdated", ctx.t("setup.plugins.otherCommit", { commit: shortCommit(row.installedCommit), approved: shortCommit(row.plugin.commit) }));
51483
51615
  if (row.state === "disabled") return s.state("missing", ctx.t("setup.plugins.disabled"));
51484
51616
  if (row.state === "missing") return s.state("missing", ctx.t("setup.status.missing"));
51485
- if (row.state === "unverified") return s.state("error", ctx.t(row.plugin.conflict ? "setup.plugins.conflict" : "setup.plugins.unverified"));
51617
+ if (row.state === "unverified") return s.state("error", ctx.t(row.plugin.conflict ? "setup.plugins.conflict" : row.marketAdded && row.installed !== null ? "setup.plugins.contentUnproven" : "setup.plugins.unverified"));
51486
51618
  if (row.state === "error") return s.state("error", ctx.t("setup.plugins.loadError"));
51487
51619
  return s.state("muted", ctx.t("setup.plugins.unchecked"));
51488
51620
  }
@@ -51498,7 +51630,7 @@ async function pluginStep(ctx, system, clients, plugins, interactive, ask2, opti
51498
51630
  for (const line of s.columns(list2.map((row) => [
51499
51631
  s.bold(`${neutralizeLine(pluginId(row.plugin))} · ${neutralizeLine(rowLabel(row))}`),
51500
51632
  stateText(ctx, row),
51501
- s.dim(neutralizeLine(row.plugin.kits.join(", ")))
51633
+ s.dim(neutralizeLine(`${row.plugin.kits.join(", ")} · ${row.plugin.commit ? shortCommit(row.plugin.commit) : ctx.t("setup.plugins.unboundShort")}`))
51502
51634
  ]))) ctx.out.line(line);
51503
51635
  };
51504
51636
  if (!ctx.json) print(rows);
@@ -51516,9 +51648,14 @@ async function pluginStep(ctx, system, clients, plugins, interactive, ask2, opti
51516
51648
  const binary = clients.find((client) => client.id === row.client)?.binary;
51517
51649
  if (!commands.length || !binary) continue;
51518
51650
  const label = `${pluginId(row.plugin)} → ${rowLabel(row)}`;
51651
+ if (auto && !row.plugin.commit) {
51652
+ if (!ctx.json) ctx.out.line(` ${s.note(ctx.t("setup.plugins.unboundAuto", { plugin: neutralizeLine(label) }))}`);
51653
+ continue;
51654
+ }
51519
51655
  if (!ctx.json) {
51520
51656
  ctx.out.line(s.bold(neutralizeLine(label)));
51521
51657
  ctx.out.line(` ${s.note(ctx.t("setup.plugins.source", { source: neutralizeLine(row.plugin.source), version: neutralizeLine(row.plugin.version) }))}`);
51658
+ ctx.out.line(` ${s.note(row.plugin.commit ? ctx.t("setup.plugins.bound", { commit: shortCommit(row.plugin.commit) }) : ctx.t("setup.plugins.unbound"))}`);
51522
51659
  for (const args of commands) ctx.out.line(` ${s.diff(`$ ${neutralizeLine(shown2(row, binary, args, system.env))}`)}`);
51523
51660
  if (auto) ctx.out.line(` ${s.note(ctx.t("setup.plugins.auto"))}`);
51524
51661
  }
@@ -51530,9 +51667,28 @@ async function pluginStep(ctx, system, clients, plugins, interactive, ask2, opti
51530
51667
  }
51531
51668
  all = answer2 === "all";
51532
51669
  }
51670
+ if (row.client === "claude" && row.state === "missing" && row.plugin.commit) {
51671
+ const upstream = await upstreamServes(system, row.plugin);
51672
+ if (upstream !== "yes") {
51673
+ failed2++;
51674
+ applied.push({ plugin: pluginId(row.plugin), client: row.client, account: row.account, ok: false, auto });
51675
+ if (!ctx.json) ctx.out.line(` ${s.fail(ctx.t("setup.failed", { server: pluginId(row.plugin), client: rowLabel(row), reason: ctx.t(upstream === "moved" ? "setup.plugins.upstreamMoved" : "setup.plugins.upstreamUnchecked") }))}`);
51676
+ continue;
51677
+ }
51678
+ }
51533
51679
  let ok = true;
51680
+ let reason = "setup.reason.command_failed";
51534
51681
  const env = homeEnv(row);
51535
- for (const args of commands) {
51682
+ for (const [index, args] of commands.entries()) {
51683
+ if (row.state === "missing" && row.plugin.commit && index === commands.length - 1) {
51684
+ const market = (await marketplaces(system, row, binary))?.get(row.plugin.marketplace);
51685
+ const verdict = market && market.source === canonicalGithub(row.plugin.source) ? await checkoutVerdict(row.client, market.root, row.plugin) : "other_commit";
51686
+ if (verdict !== "ok") {
51687
+ ok = false;
51688
+ reason = verdict === "not_local" ? "setup.plugins.notLocal" : "setup.plugins.checkoutOther";
51689
+ break;
51690
+ }
51691
+ }
51536
51692
  const result = await system.exec(binary, args, { cwd: system.home, timeoutMs: INSTALL_TIMEOUT_MS, ...env ? { env } : {} });
51537
51693
  if (result.code !== 0 || result.timedOut) {
51538
51694
  ok = false;
@@ -51542,7 +51698,7 @@ async function pluginStep(ctx, system, clients, plugins, interactive, ask2, opti
51542
51698
  if (!ok) failed2++;
51543
51699
  else if (row.state === "missing") await recordInstall(ctx.configDir, row);
51544
51700
  applied.push({ plugin: pluginId(row.plugin), client: row.client, account: row.account, ok, auto });
51545
- if (!ctx.json) ctx.out.line(` ${ok ? s.ok(ctx.t("setup.applied", { server: pluginId(row.plugin), client: rowLabel(row) })) : s.fail(ctx.t("setup.failed", { server: pluginId(row.plugin), client: rowLabel(row), reason: ctx.t("setup.reason.command_failed") }))}`);
51701
+ if (!ctx.json) ctx.out.line(` ${ok ? s.ok(ctx.t("setup.applied", { server: pluginId(row.plugin), client: rowLabel(row) })) : s.fail(ctx.t("setup.failed", { server: pluginId(row.plugin), client: rowLabel(row), reason: ctx.t(reason) }))}`);
51546
51702
  }
51547
51703
  if (applied.length) {
51548
51704
  rows = await inspectPlugins(system, clients, plugins);
@@ -51569,9 +51725,12 @@ async function usableSkills(dir) {
51569
51725
  async function approvedPluginDirs(system, plugins) {
51570
51726
  const ready = [], missing = [];
51571
51727
  if (!plugins.length) return { ready, missing };
51728
+ for (const plugin of plugins) if (!plugin.commit) missing.push({ plugin: pluginId(plugin), reason: "unbound", installed: null });
51729
+ const bound = plugins.filter((plugin) => plugin.commit);
51730
+ if (!bound.length) return { ready, missing };
51572
51731
  const binary = await system.which("claude");
51573
- if (!binary) return { ready, missing: plugins.map((plugin) => ({ plugin: pluginId(plugin), reason: "no_client", installed: null })) };
51574
- const rows = await inspectPlugins(system, [{ id: "claude", binary, version: null }], plugins, { orca: false });
51732
+ if (!binary) return { ready, missing: [...missing, ...bound.map((plugin) => ({ plugin: pluginId(plugin), reason: "no_client", installed: null }))] };
51733
+ const rows = await inspectPlugins(system, [{ id: "claude", binary, version: null }], bound, { orca: false });
51575
51734
  for (const row of rows) {
51576
51735
  const gap = (reason) => missing.push({ plugin: pluginId(row.plugin), reason, installed: row.installed });
51577
51736
  if (row.state !== "current") {
@@ -51588,7 +51747,7 @@ async function approvedPluginDirs(system, plugins) {
51588
51747
  gap("no_skills");
51589
51748
  continue;
51590
51749
  }
51591
- ready.push({ plugin: pluginId(row.plugin), name: row.plugin.name, version: row.plugin.version, dir: realDir });
51750
+ ready.push({ plugin: pluginId(row.plugin), name: row.plugin.name, version: row.plugin.version, commit: row.plugin.commit, dir: realDir });
51592
51751
  }
51593
51752
  return { ready, missing };
51594
51753
  }
@@ -51639,6 +51798,8 @@ var pluginJson = (rows) => rows.map((row) => ({
51639
51798
  plugin: pluginId(row.plugin),
51640
51799
  source: row.plugin.source,
51641
51800
  approvedVersion: row.plugin.version,
51801
+ // KIT-PLUGIN-BIND: the commit fields only for a bound plugin (an unbound row keeps its former shape)
51802
+ ...row.plugin.commit ? { approvedCommit: row.plugin.commit, installedCommit: row.installedCommit } : {},
51642
51803
  kits: row.plugin.kits,
51643
51804
  client: row.client,
51644
51805
  account: row.account,
@@ -51694,7 +51855,7 @@ async function text4(file) {
51694
51855
  try {
51695
51856
  const info = await lstat11(file);
51696
51857
  if (!info.isFile() || info.size > 2 * 1024 * 1024) throw failure();
51697
- return await readFile15(file, "utf8");
51858
+ return await readFile16(file, "utf8");
51698
51859
  } catch (error) {
51699
51860
  if (error.code === "ENOENT") return null;
51700
51861
  throw error;
@@ -51981,7 +52142,7 @@ function revocationPage(profile) {
51981
52142
 
51982
52143
  // src/orca-setup.ts
51983
52144
  import { constants as fsConstants3 } from "node:fs";
51984
- import { access as access4, mkdir as mkdir6, readFile as readFile16, readdir as readdir11, realpath as realpath13, stat as stat12 } from "node:fs/promises";
52145
+ import { access as access4, mkdir as mkdir6, readFile as readFile17, readdir as readdir11, realpath as realpath13, stat as stat12 } from "node:fs/promises";
51985
52146
  import { dirname as dirname15, join as join23, sep as sep7 } from "node:path";
51986
52147
  var WRAPPER_CLIENTS = ["claude", "codex"];
51987
52148
  async function orcaPresent2(system) {
@@ -52013,7 +52174,7 @@ async function wrapperStates(system, configDir, fleek) {
52013
52174
  const path = join23(wrapperDir(configDir), `fleek-${client}`);
52014
52175
  const program = await realClient(system, client, configDir);
52015
52176
  const content3 = program && fleek && await isStableFleek(fleek) ? wrapperContent(fleek, client, program) : null;
52016
- const current2 = await readFile16(path, "utf8").catch(() => null);
52177
+ const current2 = await readFile17(path, "utf8").catch(() => null);
52017
52178
  const executable = current2 !== null && await access4(path, fsConstants3.X_OK).then(() => true, () => false);
52018
52179
  out.push({ client, path, program, content: content3, before: current2, state: !content3 ? "unavailable" : current2 === null ? "missing" : current2 === content3 && executable ? "current" : "outdated" });
52019
52180
  }
@@ -52022,7 +52183,7 @@ async function wrapperStates(system, configDir, fleek) {
52022
52183
  async function writeWrapper(state, configDir) {
52023
52184
  if (!state.content || state.state === "current") return false;
52024
52185
  await mkdir6(dirname15(state.path), { recursive: true, mode: 448 });
52025
- const before = await readFile16(state.path, "utf8").catch((error) => {
52186
+ const before = await readFile17(state.path, "utf8").catch((error) => {
52026
52187
  if (error.code === "ENOENT") return null;
52027
52188
  throw error;
52028
52189
  });
@@ -52187,7 +52348,7 @@ async function linkOpen(deps, url, values = {}) {
52187
52348
  }
52188
52349
 
52189
52350
  // src/link-handler.ts
52190
- import { mkdir as mkdir7, readFile as readFile17, rm as rm9, stat as stat14, writeFile as writeFile3 } from "node:fs/promises";
52351
+ import { mkdir as mkdir7, readFile as readFile18, rm as rm9, stat as stat14, writeFile as writeFile3 } from "node:fs/promises";
52191
52352
  import { delimiter as delimiter3, dirname as dirname17, join as join24 } from "node:path";
52192
52353
  var LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
52193
52354
  var BUNDLE_ID = "app.onfleek.fleek-link";
@@ -52278,7 +52439,7 @@ async function installLinkHandler(system, fleek) {
52278
52439
  return { platform: system.platform, paths: [launcher, app] };
52279
52440
  }
52280
52441
  const desktop = desktopPath(system);
52281
- const before = await readFile17(desktop, "utf8").catch(() => null);
52442
+ const before = await readFile18(desktop, "utf8").catch(() => null);
52282
52443
  if (before !== null && !before.includes(launcher)) throw failed("desktop");
52283
52444
  const xdgMime = await system.which("xdg-mime");
52284
52445
  if (!xdgMime) throw failed("xdg-mime");
@@ -52302,7 +52463,7 @@ async function removeLinkHandler(system) {
52302
52463
  }
52303
52464
  } else {
52304
52465
  const desktop = desktopPath(system);
52305
- if ((await readFile17(desktop, "utf8").catch(() => "")).includes(launcher)) {
52466
+ if ((await readFile18(desktop, "utf8").catch(() => "")).includes(launcher)) {
52306
52467
  await rm9(desktop, { force: true });
52307
52468
  removed = true;
52308
52469
  }
@@ -52447,7 +52608,6 @@ var Session = class {
52447
52608
  };
52448
52609
 
52449
52610
  // src/header.ts
52450
- import { homedir as homedir2 } from "node:os";
52451
52611
  import { win32, posix } from "node:path";
52452
52612
  var LOGO2 = [" ▄██████▀", " ▄█████▀ ", " ██▀ "];
52453
52613
  var ASCII_LOGO = [" ######/", " #####/ ", " ##/ "];
@@ -52480,9 +52640,9 @@ function fit(value, width, ascii) {
52480
52640
  return out + suffix;
52481
52641
  }
52482
52642
  var safe = (value) => neutralizeLine(redact(value));
52483
- function directory(context, windows) {
52643
+ function directory(context, windows, env) {
52484
52644
  const path = windows ? win32 : posix;
52485
- const home = context.home ?? homedir2();
52645
+ const home = context.home ?? userHome(env, windows ? "win32" : process.platform);
52486
52646
  const relative3 = path.relative(home, context.cwd);
52487
52647
  const inside = relative3 === "" || relative3 !== ".." && !relative3.startsWith(`..${path.sep}`) && !path.isAbsolute(relative3);
52488
52648
  return safe(inside ? relative3 ? `~${path.sep}${relative3}` : "~" : context.cwd);
@@ -52503,7 +52663,7 @@ function renderHeader(context, terminal, t) {
52503
52663
  const requested = terminal.stream.columns ?? Number(terminal.env.COLUMNS);
52504
52664
  const columns = Number.isFinite(requested) && requested > 0 ? Math.floor(requested) : 80;
52505
52665
  const title = t("header.title", { version: safe(context.version) });
52506
- const cwd = directory(context, windows);
52666
+ const cwd = directory(context, windows, terminal.env);
52507
52667
  const parts = [context.workspace, context.project, headerServer(context.server), context.displayName].filter((value) => !!value);
52508
52668
  const details = parts.length ? parts.map(safe).join(separator) : t("header.noContext");
52509
52669
  if (columns < 60) return [fit([title, cwd].join(separator), columns, ascii)];
@@ -52545,8 +52705,7 @@ function canonicalId(value) {
52545
52705
  // src/kit.ts
52546
52706
  import { spawn as spawn7 } from "node:child_process";
52547
52707
  import { randomBytes as randomBytes10 } from "node:crypto";
52548
- import { readFile as readFile19, realpath as realpath15 } from "node:fs/promises";
52549
- import { homedir as homedir4 } from "node:os";
52708
+ import { readFile as readFile20, realpath as realpath15 } from "node:fs/promises";
52550
52709
  import { isAbsolute as isAbsolute9, join as join28, resolve as resolve6, sep as sep8 } from "node:path";
52551
52710
  var import_types5 = __toESM(require_dist(), 1);
52552
52711
 
@@ -52565,7 +52724,6 @@ function trustFor(set, issuer2) {
52565
52724
  }
52566
52725
 
52567
52726
  // src/kit-setup.ts
52568
- import { homedir as homedir3 } from "node:os";
52569
52727
  import { join as join26, resolve as resolve5 } from "node:path";
52570
52728
 
52571
52729
  // src/init-scan.ts
@@ -52619,7 +52777,7 @@ async function readCheckout(cwd) {
52619
52777
  const files = (await git(root, ["ls-files", "-z"])).toString("utf8").split("\0").filter(Boolean).slice(0, INIT_SCAN_LIMITS.trackedFiles);
52620
52778
  return { root, commit, dirty, files };
52621
52779
  }
52622
- var MANIFESTS = ["package.json", "composer.json", "go.mod", "pyproject.toml", "Gemfile", "pom.xml", "build.gradle", "build.gradle.kts"];
52780
+ var MANIFESTS2 = ["package.json", "composer.json", "go.mod", "pyproject.toml", "Gemfile", "pom.xml", "build.gradle", "build.gradle.kts"];
52623
52781
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "vendor", "dist", "build", ".git", ".next", "out", "coverage", ".turbo"]);
52624
52782
  var APP_DEPENDENCIES = /* @__PURE__ */ new Set([
52625
52783
  "next",
@@ -52654,7 +52812,7 @@ function ignored(path) {
52654
52812
  return path.split("/").some((segment) => IGNORED_DIRS.has(segment));
52655
52813
  }
52656
52814
  async function readJson(root, path) {
52657
- const text6 = await readSmall(root, path, INIT_SCAN_LIMITS.manifestBytes);
52815
+ const text6 = await readSmall2(root, path, INIT_SCAN_LIMITS.manifestBytes);
52658
52816
  if (text6 === null) return null;
52659
52817
  try {
52660
52818
  const value = JSON.parse(text6);
@@ -52663,7 +52821,7 @@ async function readJson(root, path) {
52663
52821
  return null;
52664
52822
  }
52665
52823
  }
52666
- async function readSmall(root, path, max) {
52824
+ async function readSmall2(root, path, max) {
52667
52825
  try {
52668
52826
  if (!(0, import_validation.evidencePathAllowed)(path)) return null;
52669
52827
  const realRoot = await realpath14(root), source = join25(root, path);
@@ -52711,7 +52869,7 @@ async function detectApplications(checkout) {
52711
52869
  for (const file of checkout.files) {
52712
52870
  if (ignored(file)) continue;
52713
52871
  const name = posix2.basename(file);
52714
- if (!MANIFESTS.includes(name)) continue;
52872
+ if (!MANIFESTS2.includes(name)) continue;
52715
52873
  const dir = posix2.dirname(file) === "." ? "" : posix2.dirname(file);
52716
52874
  if (dir.split("/").filter(Boolean).length > INIT_SCAN_LIMITS.depth) continue;
52717
52875
  dirs.add(dir);
@@ -52840,17 +52998,17 @@ async function scanFacts(checkout, app) {
52840
52998
  }
52841
52999
  const goPath = `${prefix}go.mod`;
52842
53000
  if (own4.includes(goPath)) {
52843
- const text6 = await readSmall(checkout.root, goPath, INIT_SCAN_LIMITS.manifestBytes);
53001
+ const text6 = await readSmall2(checkout.root, goPath, INIT_SCAN_LIMITS.manifestBytes);
52844
53002
  add("language", "go", majorOf(text6?.match(/^go\s+(\d+\.\d+)/m)?.[1]) ?? null, goPath);
52845
53003
  }
52846
53004
  const pyPath = `${prefix}pyproject.toml`;
52847
53005
  if (own4.includes(pyPath)) {
52848
- const text6 = await readSmall(checkout.root, pyPath, INIT_SCAN_LIMITS.manifestBytes) ?? "";
53006
+ const text6 = await readSmall2(checkout.root, pyPath, INIT_SCAN_LIMITS.manifestBytes) ?? "";
52849
53007
  add("language", "python", majorOf(text6.match(/requires-python\s*=\s*"([^"]*)"/)?.[1]), pyPath);
52850
53008
  for (const [dependency, product] of [["django", "django"], ["fastapi", "fastapi"], ["flask", "flask"]]) if (new RegExp(`["\\s]${dependency}[>=<~!\\s"]`, "i").test(text6)) add("framework", product, null, pyPath);
52851
53009
  }
52852
53010
  if (own4.includes(`${prefix}Gemfile`)) {
52853
- const text6 = await readSmall(checkout.root, `${prefix}Gemfile`, INIT_SCAN_LIMITS.manifestBytes) ?? "";
53011
+ const text6 = await readSmall2(checkout.root, `${prefix}Gemfile`, INIT_SCAN_LIMITS.manifestBytes) ?? "";
52854
53012
  add("language", "ruby", null, `${prefix}Gemfile`);
52855
53013
  if (/gem\s+['"]rails['"]/.test(text6)) add("framework", "rails", majorOf(text6.match(/gem\s+['"]rails['"]\s*,\s*['"][~>=\s]*([\d.]+)/)?.[1]), `${prefix}Gemfile`);
52856
53014
  }
@@ -52859,7 +53017,7 @@ async function scanFacts(checkout, app) {
52859
53017
  if (fsMarker) add("cms", "firstspirit", null, fsMarker);
52860
53018
  else {
52861
53019
  for (const file of own4.filter((name) => /(?:^|\/)(?:pom\.xml|build\.gradle(?:\.kts)?|module(?:-isolated)?\.xml)$/.test(name)).slice(0, 20)) {
52862
- const text6 = await readSmall(checkout.root, file, INIT_SCAN_LIMITS.manifestBytes);
53020
+ const text6 = await readSmall2(checkout.root, file, INIT_SCAN_LIMITS.manifestBytes);
52863
53021
  if (text6 && /de\.espirit|fs-isolated-runtime|fs-isolated-client|fs-access\b/i.test(text6)) {
52864
53022
  add("cms", "firstspirit", null, file);
52865
53023
  break;
@@ -52901,7 +53059,7 @@ async function scanFacts(checkout, app) {
52901
53059
  if (rootPath && patternFiles[0] && (0, import_validation.evidencePathAllowed)(rootPath)) designSources.components = { path: rootPath, evidence: [patternFiles[0]] };
52902
53060
  const figmaSources = own4.filter((file) => /(?:^|\/)figma\.config\.json$|\.figma\.[cm]?[jt]sx?$/.test(file) && (0, import_validation.evidencePathAllowed)(file)).slice(0, 3);
52903
53061
  for (const file of figmaSources) {
52904
- const content3 = await readSmall(checkout.root, file, INIT_SCAN_LIMITS.fileBytes);
53062
+ const content3 = await readSmall2(checkout.root, file, INIT_SCAN_LIMITS.fileBytes);
52905
53063
  const url = content3?.match(/https:\/\/(?:www\.)?figma\.com\/(?:file|design)\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)?/)?.[0];
52906
53064
  if (url && (0, import_validation.environmentUrlAllowed)(url)) {
52907
53065
  designSources.figmaFile = { url, evidence: [file] };
@@ -52943,14 +53101,14 @@ async function scanFacts(checkout, app) {
52943
53101
  }
52944
53102
  if (/(?:^|\/)k8s\/.*\.ya?ml$/.test(file)) add("hosting", "kubernetes", null, file);
52945
53103
  if (/\.tf$/.test(name)) {
52946
- const text6 = await readSmall(checkout.root, file, INIT_SCAN_LIMITS.fileBytes) ?? "";
53104
+ const text6 = await readSmall2(checkout.root, file, INIT_SCAN_LIMITS.fileBytes) ?? "";
52947
53105
  for (const match of text6.matchAll(/\bprovider\s+"(aws|google|azurerm|hcloud)"/g)) add("hosting", match[1], null, file);
52948
53106
  }
52949
53107
  const suffix = /^(?:docker-compose|compose)[.-]([A-Za-z][A-Za-z0-9_-]{0,59})\.ya?ml$/.exec(name);
52950
53108
  if (suffix) environment(suffix[1], file);
52951
53109
  if (/^\.github\/workflows\/[^/]+\.ya?ml$/.test(file)) {
52952
53110
  add("ci", "github-actions", null, file);
52953
- const text6 = await readSmall(checkout.root, file, INIT_SCAN_LIMITS.fileBytes) ?? "";
53111
+ const text6 = await readSmall2(checkout.root, file, INIT_SCAN_LIMITS.fileBytes) ?? "";
52954
53112
  for (const match of text6.matchAll(/^\s*environment:\s*["']?([A-Za-z][A-Za-z0-9_-]{0,59})["']?\s*(?:#.*)?$/gm)) environment(match[1], file);
52955
53113
  }
52956
53114
  for (const [ciName, product] of [
@@ -53400,7 +53558,7 @@ var PROVISION_ACTION_NEEDED = 13;
53400
53558
  var CLIENT_OF = { claude: "claude", orca: "claude", codex: "codex", cursor: "cursor" };
53401
53559
  var LABEL4 = { claude: "Claude Code", codex: "Codex", cursor: "Cursor" };
53402
53560
  function systemOf(ctx) {
53403
- return ctx.deps.provision ?? defaultSystem(ctx.deps.home ?? homedir3(), ctx.env);
53561
+ return ctx.deps.provision ?? defaultSystem(contextHome(ctx), ctx.env);
53404
53562
  }
53405
53563
  var isTty = (ctx) => ctx.deps.isTTY ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
53406
53564
  async function projectFacts(checkout, session, projectId) {
@@ -54149,7 +54307,7 @@ async function kitDoctor(ctx, values) {
54149
54307
  }
54150
54308
 
54151
54309
  // src/kit-proxy-launch.ts
54152
- import { mkdir as mkdir9, mkdtemp, readFile as readFile18, rm as rm10, writeFile as writeFile5 } from "node:fs/promises";
54310
+ import { mkdir as mkdir9, mkdtemp, readFile as readFile19, rm as rm10, writeFile as writeFile5 } from "node:fs/promises";
54153
54311
  import { dirname as dirname19, join as join27 } from "node:path";
54154
54312
  async function proxyLaunchPlugin(input) {
54155
54313
  const unchanged = { pluginDir: input.pluginDir, cleanup: async () => {
@@ -54157,7 +54315,7 @@ async function proxyLaunchPlugin(input) {
54157
54315
  if (!input.files.includes("plugin/.mcp.json")) return unchanged;
54158
54316
  let config;
54159
54317
  try {
54160
- config = JSON.parse(await readFile18(join27(input.pluginDir, ".mcp.json"), "utf8"));
54318
+ config = JSON.parse(await readFile19(join27(input.pluginDir, ".mcp.json"), "utf8"));
54161
54319
  } catch {
54162
54320
  throw new CliError("errors.kitStateCorrupt", EXIT.failed, { path: input.pluginDir }, "KIT_STATE_CORRUPT");
54163
54321
  }
@@ -54175,7 +54333,7 @@ async function proxyLaunchPlugin(input) {
54175
54333
  }
54176
54334
  const destination = join27(pluginDir, relative3);
54177
54335
  await mkdir9(dirname19(destination), { recursive: true, mode: 448 });
54178
- const bytes = relative3 === ".mcp.json" ? JSON.stringify({ ...config, mcpServers: { ...config.mcpServers, fleek: { type: "stdio", command: input.proxy.command, args: [...input.proxy.args], ...input.proxy.env ? { env: input.proxy.env } : {} } } }, null, 2) + "\n" : await readFile18(join27(input.pluginDir, relative3));
54336
+ const bytes = relative3 === ".mcp.json" ? JSON.stringify({ ...config, mcpServers: { ...config.mcpServers, fleek: { type: "stdio", command: input.proxy.command, args: [...input.proxy.args], ...input.proxy.env ? { env: input.proxy.env } : {} } } }, null, 2) + "\n" : await readFile19(join27(input.pluginDir, relative3));
54179
54337
  await writeFile5(destination, bytes, { flag: "wx", mode: 384 });
54180
54338
  }
54181
54339
  return { pluginDir, cleanup };
@@ -54317,7 +54475,7 @@ async function globalE1(home, target) {
54317
54475
  const spec = KIT_CAPABILITIES[target].globalE1;
54318
54476
  if (!spec) return false;
54319
54477
  try {
54320
- const text6 = await readFile19(join28(home, spec.path), "utf8");
54478
+ const text6 = await readFile20(join28(home, spec.path), "utf8");
54321
54479
  return spec.contains ? text6.includes(spec.contains) : true;
54322
54480
  } catch {
54323
54481
  return false;
@@ -54539,7 +54697,7 @@ async function checkoutInventory(domain, current2) {
54539
54697
  }
54540
54698
  async function writeComposition(input) {
54541
54699
  const { ctx, domain, profile, current: current2, kits, targets } = input;
54542
- const home = ctx.deps.home ?? homedir4();
54700
+ const home = contextHome(ctx);
54543
54701
  const checkoutMode = input.unattendedRemoval ? current2?.state.checkoutMode ?? false : input.checkoutRoot !== null;
54544
54702
  const generation = await composeAll(kits, targets, checkoutMode, home, domain);
54545
54703
  const pending = await domain.pending();
@@ -54951,7 +55109,7 @@ async function kitStatus(ctx, values, options = {}) {
54951
55109
  const projectId = project.projectId;
54952
55110
  const former = options.quiet ? void 0 : project.former ?? await settleFormerProjects(ctx, profileHint, projectId);
54953
55111
  const domain = await prepareDomain(ctx, profileHint, projectId);
54954
- const home = ctx.deps.home ?? homedir4();
55112
+ const home = contextHome(ctx);
54955
55113
  const lastChecked = await domain.checkedAt();
54956
55114
  const current2 = await domain.current();
54957
55115
  const e1 = Object.fromEntries(await Promise.all(Object.entries(current2?.state.targets ?? {}).map(async ([target, state]) => {
@@ -55188,7 +55346,7 @@ async function kitRun(ctx, client, values, userArgs) {
55188
55346
  const state = gen.state.targets[target];
55189
55347
  if (!state) throw new CliError("errors.kitTargetNotPulled", EXIT.failed, { target }, "KIT_NOT_PULLED");
55190
55348
  const bundleDir = join28(gen.dir, "bundle", target);
55191
- const readOptional = (rel) => state.files.includes(rel) ? readFile19(join28(bundleDir, rel), "utf8") : Promise.resolve(null);
55349
+ const readOptional = (rel) => state.files.includes(rel) ? readFile20(join28(bundleDir, rel), "utf8") : Promise.resolve(null);
55192
55350
  const instructions3 = await readOptional("instructions.md");
55193
55351
  const mcpRaw = await readOptional("mcp.json");
55194
55352
  let bundle = {
@@ -55198,7 +55356,7 @@ async function kitRun(ctx, client, values, userArgs) {
55198
55356
  };
55199
55357
  let runtime;
55200
55358
  try {
55201
- const pluginMcp = state.files.includes("plugin/.mcp.json") ? JSON.parse(await readFile19(join28(bundleDir, "plugin/.mcp.json"), "utf8")) : null;
55359
+ const pluginMcp = state.files.includes("plugin/.mcp.json") ? JSON.parse(await readFile20(join28(bundleDir, "plugin/.mcp.json"), "utf8")) : null;
55202
55360
  const reserved = bundle.mcpServers.some((server) => server.name === FLEEK_SERVER) || !!pluginMcp?.mcpServers && Object.hasOwn(pluginMcp.mcpServers, FLEEK_SERVER);
55203
55361
  if (reserved) {
55204
55362
  const executable = profile.cliPath ?? await resolveExecutable("fleek", ctx.env);
@@ -55226,7 +55384,7 @@ async function kitRun(ctx, client, values, userArgs) {
55226
55384
  if (skills.size && !skillRoot) throw new CliError("errors.kitRunCodexCheckout", EXIT.failed, { reason: "no_checkout", target }, "KIT_TARGET_UNSUPPORTED");
55227
55385
  if (values["dry-run"]) {
55228
55386
  const plugins = target === "claude" ? await generationPlugins(domain, gen) : [];
55229
- const vendor = await approvedPluginDirs(ctx.deps.provision ?? defaultSystem(ctx.deps.home ?? homedir4(), ctx.env), plugins);
55387
+ const vendor = await approvedPluginDirs(ctx.deps.provision ?? defaultSystem(contextHome(ctx), ctx.env), plugins);
55230
55388
  ctx.out.json({
55231
55389
  ...command,
55232
55390
  ...skillRoot ? { codexSkills: { root: skillRoot, skills: [...skills.keys()].sort() } } : {},
@@ -55884,7 +56042,7 @@ var import_types6 = __toESM(require_dist(), 1);
55884
56042
  import { spawn as spawn8 } from "node:child_process";
55885
56043
  import { randomBytes as randomBytes11, randomUUID as randomUUID5 } from "node:crypto";
55886
56044
  import { constants as fsConstants6 } from "node:fs";
55887
- import { access as access5, chmod as chmod4, chown, lstat as lstat14, open as open7, readFile as readFile20, rename as rename5, rm as rm12, stat as stat15 } from "node:fs/promises";
56045
+ import { access as access5, chmod as chmod4, chown, lstat as lstat14, open as open7, readFile as readFile21, rename as rename5, rm as rm12, stat as stat15 } from "node:fs/promises";
55888
56046
  import { hostname as osHostname } from "node:os";
55889
56047
  import { delimiter as delimiter4, dirname as dirname21, join as join30 } from "node:path";
55890
56048
  var SERVICE_KIND = "review";
@@ -56818,7 +56976,7 @@ function realSystem(options = {}) {
56818
56976
  },
56819
56977
  exists: (path) => lstat14(path).then(() => true, () => false),
56820
56978
  entry: (path) => lstat14(path).then((info) => ({ uid: info.uid, mode: info.mode & 4095, symlink: info.isSymbolicLink() }), () => null),
56821
- readText: (path) => readFile20(path, "utf8").catch(() => null),
56979
+ readText: (path) => readFile21(path, "utf8").catch(() => null),
56822
56980
  async writeAtomic(path, content3, options2) {
56823
56981
  const temp = join30(dirname21(path), `.${randomBytes11(6).toString("hex")}.tmp`);
56824
56982
  const handle = await open7(temp, "wx", options2.mode);
@@ -56867,13 +57025,13 @@ function realSystem(options = {}) {
56867
57025
 
56868
57026
  // src/setup.ts
56869
57027
  var import_types7 = __toESM(require_dist(), 1);
56870
- import { hostname, homedir as homedir7 } from "node:os";
57028
+ import { hostname } from "node:os";
56871
57029
  import { join as join32, resolve as resolve8 } from "node:path";
56872
57030
  import { realpath as realpath18 } from "node:fs/promises";
56873
57031
  import { createInterface } from "node:readline/promises";
56874
57032
 
56875
57033
  // src/setup-install.ts
56876
- import { homedir as homedir5 } from "node:os";
57034
+ import { homedir as homedir2 } from "node:os";
56877
57035
  import { isAbsolute as isAbsolute10, join as join31 } from "node:path";
56878
57036
  import { realpath as realpath17 } from "node:fs/promises";
56879
57037
  async function installSetupCli(input) {
@@ -56884,7 +57042,7 @@ async function installSetupCli(input) {
56884
57042
  if (!npm) throw new CliError("errors.setupNpm", EXIT.unavailable, {}, "NPM_UNAVAILABLE");
56885
57043
  const process2 = input.run ?? runInitProcess;
56886
57044
  const run2 = (program, args, options2) => args[0] === "install" && input.waiting ? input.waiting(() => process2(program, args, options2)) : process2(program, args, options2);
56887
- const cwd = input.home ?? homedir5();
57045
+ const cwd = input.home ?? homedir2();
56888
57046
  const options = { cwd, env: input.env, signal: input.signal, timeoutMs: 18e4, maxBytes: 256 * 1024 };
56889
57047
  const prefixResult = await run2(npm, ["prefix", "--global"], options);
56890
57048
  const prefix = prefixResult.code === 0 ? prefixResult.stdout.trim() : "";
@@ -56926,9 +57084,8 @@ async function installSetupCli(input) {
56926
57084
  }
56927
57085
 
56928
57086
  // src/setup-proxy.ts
56929
- import { homedir as homedir6 } from "node:os";
56930
57087
  function createSetupProxyAdapter(ctx) {
56931
- const system = ctx.deps.provision ?? defaultSystem(ctx.deps.home ?? homedir6(), ctx.env);
57088
+ const system = ctx.deps.provision ?? defaultSystem(contextHome(ctx), ctx.env);
56932
57089
  let configured = [];
56933
57090
  return {
56934
57091
  async provision(input) {
@@ -57049,7 +57206,7 @@ async function setupCommand(ctx, code, values) {
57049
57206
  rl.close();
57050
57207
  }
57051
57208
  };
57052
- const folderPath = (value) => value === "~" ? ctx.deps.home ?? homedir7() : value.startsWith("~/") ? join32(ctx.deps.home ?? homedir7(), value.slice(2)) : resolve8(ctx.deps.cwd ?? process.cwd(), value);
57209
+ const folderPath = (value) => value === "~" ? contextHome(ctx) : value.startsWith("~/") ? join32(contextHome(ctx), value.slice(2)) : resolve8(ctx.deps.cwd ?? process.cwd(), value);
57053
57210
  let cwd = folderPath(values.checkout ?? ".");
57054
57211
  let checkout = await readCheckout(cwd);
57055
57212
  if (!checkout) {
@@ -57143,7 +57300,7 @@ async function setupCommand(ctx, code, values) {
57143
57300
  version: claim.cliVersion,
57144
57301
  runningVersion: ctx.version,
57145
57302
  env: ctx.env,
57146
- home: ctx.deps.home ?? homedir7(),
57303
+ home: contextHome(ctx),
57147
57304
  signal: ctx.deps.signal,
57148
57305
  run: ctx.deps.setupProcess,
57149
57306
  waiting: (work2) => ctx.out.waiting(t("progress.install"), work2),
@@ -57161,7 +57318,7 @@ async function setupCommand(ctx, code, values) {
57161
57318
  throw new CliError("errors.setupCheckoutBound", EXIT.usage, {}, "CHECKOUT_ALREADY_BOUND");
57162
57319
  }
57163
57320
  await index.set(cwd, claim.project.id);
57164
- const system2 = ctx.deps.provision ?? defaultSystem(ctx.deps.home ?? homedir7(), ctx.env);
57321
+ const system2 = ctx.deps.provision ?? defaultSystem(contextHome(ctx), ctx.env);
57165
57322
  kitContext2 = { ...fixed, env: { ...fixed.env, FLEEK_CONFIG_DIR: ctx.configDir }, deps: {
57166
57323
  ...fixed.deps,
57167
57324
  provision: { ...system2, which: (name) => name === "fleek" ? Promise.resolve(cliPath) : system2.which(name) }
@@ -57227,7 +57384,7 @@ async function setupCommand(ctx, code, values) {
57227
57384
  await report("done", "ok");
57228
57385
  ctx.info("");
57229
57386
  ctx.info(ctx.out.style.ok(ctx.out.style.bold(t("wizard.done", { project: neutralizeLine(claim.project.key) }))));
57230
- const system = ctx.deps.provision ?? defaultSystem(ctx.deps.home ?? homedir7(), ctx.env);
57387
+ const system = ctx.deps.provision ?? defaultSystem(contextHome(ctx), ctx.env);
57231
57388
  const clients = await detectClients(system).catch(() => []);
57232
57389
  const runnable = clients.some((client2) => client2.id === "claude") ? "claude" : clients.some((client2) => client2.id === "codex") ? "codex" : null;
57233
57390
  const real = (path) => path ? realpath18(path).catch(() => null) : Promise.resolve(null);
@@ -57557,7 +57714,7 @@ var Context = class {
57557
57714
  this.env = deps.env ?? process.env;
57558
57715
  this.out = new Output(deps.stdout, deps.stderr, { env: this.env, json: json2 });
57559
57716
  this.t = translator(locale(this.env));
57560
- this.home = deps.home ?? (process.platform === "win32" ? this.env.USERPROFILE ?? this.env.HOME : this.env.HOME) ?? homedir8();
57717
+ this.home = deps.home ?? userHome(this.env);
57561
57718
  this.configDir = defaultConfigDir(this.env, process.platform, this.home);
57562
57719
  this.profiles = new ProfileStore(this.configDir);
57563
57720
  this.fetch = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
@@ -58045,8 +58202,8 @@ async function processTemplate(ctx, sub, args, values) {
58045
58202
  const { client } = await ctx.session();
58046
58203
  const id = () => claimRef(args[0]);
58047
58204
  const file = async () => {
58048
- const { readFile: readFile21 } = await import("node:fs/promises");
58049
- const raw = await readFile21(text5(values.file, 1024), "utf8");
58205
+ const { readFile: readFile22 } = await import("node:fs/promises");
58206
+ const raw = await readFile22(text5(values.file, 1024), "utf8");
58050
58207
  if (Buffer.byteLength(raw) > 256 * 1024) throw usage5();
58051
58208
  try {
58052
58209
  return JSON.parse(raw);
@@ -58095,8 +58252,8 @@ async function workflow(ctx, sub, args, values) {
58095
58252
  const id = () => claimRef(args[0]);
58096
58253
  const spec = async () => {
58097
58254
  const path = text5(values.file, 1024);
58098
- const { readFile: readFile21 } = await import("node:fs/promises");
58099
- const raw = await readFile21(path, "utf8");
58255
+ const { readFile: readFile22 } = await import("node:fs/promises");
58256
+ const raw = await readFile22(path, "utf8");
58100
58257
  if (Buffer.byteLength(raw) > 200 * 1024) throw usage5();
58101
58258
  try {
58102
58259
  const value = JSON.parse(raw);
@@ -58526,7 +58683,7 @@ function orcaDeps(ctx) {
58526
58683
  configDir: ctx.configDir,
58527
58684
  isTTY: ctx.deps.isTTY ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
58528
58685
  sessionFor: async (profile) => (await ctx.sessionFor(profile)).client,
58529
- which: (name) => defaultSystem(homedir8(), ctx.env).which(name),
58686
+ which: (name) => defaultSystem(ctx.home, ctx.env).which(name),
58530
58687
  ...ctx.deps.orcaExec ? { execFile: ctx.deps.orcaExec } : {},
58531
58688
  ask: ctx.deps.orcaAsk ?? (async (question) => {
58532
58689
  const { createInterface: createInterface2 } = await import("node:readline/promises");
@@ -58574,7 +58731,7 @@ async function link2(ctx, url, values) {
58574
58731
  return await linkOpen(deps, url, values.agent !== void 0 ? { agent: values.agent } : {});
58575
58732
  } catch (error) {
58576
58733
  if (!deps.isTTY && error instanceof CliError && error.code !== "LINK_INVALID") {
58577
- const dialog = deps.dialog ?? systemDialog(process.platform, deps.which, (program, args) => defaultExec(program, args, { cwd: homedir8(), env: orcaEnv(ctx.env), timeoutMs: 6e5 }));
58734
+ const dialog = deps.dialog ?? systemDialog(process.platform, deps.which, (program, args) => defaultExec(program, args, { cwd: ctx.home, env: orcaEnv(ctx.env), timeoutMs: 6e5 }));
58578
58735
  const params = Object.fromEntries(Object.entries(error.params).map(([key, value]) => [key, typeof value === "string" ? neutralizeLine(value) : value]));
58579
58736
  await dialog.notify(redact(ctx.t(error.key, params))).catch(() => {
58580
58737
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logic-joe/fleek",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "fleek - command line client for the Fleek agent hub (MCP over OAuth, OS keychain, signed role/project kits)",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -9,6 +9,8 @@
9
9
  # (service account: the admin's approval of the signed kit is the confirmation; needs the `claude` CLI on the host, used only for
10
10
  # `plugin install|list`); run-job copies their skill Markdown read-only into the review container. Ready only when every declared
11
11
  # plugin is installed in the approved version with a skills/ directory (VF-R2: `kit run --dry-run` names each gap with its reason).
12
+ # KIT-PLUGIN-BIND: only a plugin bound to a commit in the signed kit is installed here, only while its source serves exactly that
13
+ # commit, and used only when Claude Code records that commit; an unbound plugin is reported "unbound" (the admin binds it in the kit).
12
14
  # Usage: sudo /opt/fleek-agents/kit-setup review (then: systemctl restart fleek-agent-review.service)
13
15
  set -euo pipefail
14
16
  if [ "${1:-}" != --as-broker ]; then
@@ -72,6 +74,7 @@ for gap in json.load(sys.stdin).get("vendorPluginsMissing", [])[:5]:
72
74
  if [ -n "$gaps" ]; then
73
75
  printf 'run-job is NOT ready for the vendor skills of the approved kits (the job would run without them):\n%s\n' "$gaps" >&2
74
76
  echo 'Fix: install / enable the approved version for this account (re-run this setup), or ask the admin to approve the installed version.' >&2
77
+ echo 'unbound / other_commit: the admin binds the plugin to the commit the source serves (new kit version); nothing unbound is installed here.' >&2
75
78
  exit 13
76
79
  fi
77
80
  # Exit 13 of `kit setup` here only means optional host entries stayed open (e.g. MCP proposals answered N); the job needs none.
@@ -160,7 +160,8 @@ def host_report(fleek, provider_auth, kit=None, last_job_at=None, timeout=20):
160
160
  PLUGIN_NAME = re.compile(r"[a-z0-9][a-z0-9._-]{0,63}")
161
161
  PLUGIN_VERSION = re.compile(r"[0-9A-Za-z.+-]{1,64}")
162
162
  PLUGIN_ID = re.compile(r"[a-z0-9][a-z0-9._-]{0,63}@[a-z0-9][a-z0-9._-]{0,63}")
163
- VENDOR_GAP_REASONS = ("other_version", "disabled", "missing", "unchecked", "unverified", "error", "no_client", "outside_cache", "no_skills")
163
+ # KIT-PLUGIN-BIND: "unbound" (no commit in the signed kit: never installed here) and "other_commit" (the install is not the bound commit)
164
+ VENDOR_GAP_REASONS = ("other_version", "other_commit", "unbound", "disabled", "missing", "unchecked", "unverified", "error", "no_client", "outside_cache", "no_skills")
164
165
  VENDOR_MAX_FILES, VENDOR_MAX_BYTES, VENDOR_MAX_ENTRIES, VENDOR_MAX_FILE = 2000, 8 * 1024 * 1024, 10000, 1024 * 1024
165
166
 
166
167