@tankpkg/cli 0.14.4 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/tank.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as VERSION, i as USER_AGENT, l as setConfig, n as flushLogs, o as getConfig, s as getConfigDir, t as authFlowLog } from "../debug-logger-DR0pKCTH.js";
2
+ import { a as VERSION, i as USER_AGENT, l as setConfig, n as flushLogs, o as getConfig, s as getConfigDir, t as authFlowLog } from "../debug-logger-CxX7rakF.js";
3
3
  import { t as logger } from "../logger-BhULz3Uz.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { Command } from "commander";
@@ -13,9 +13,9 @@ import ora from "ora";
13
13
  import { confirm, input } from "@inquirer/prompts";
14
14
  import { execSync, spawn } from "node:child_process";
15
15
  import crypto$1 from "node:crypto";
16
+ import { mkdir, mkdtemp, rm, stat } from "node:fs/promises";
16
17
  import { create, extract } from "tar";
17
18
  import { createInterface } from "node:readline";
18
- import { mkdir, mkdtemp, rm, stat } from "node:fs/promises";
19
19
  import { Readable } from "node:stream";
20
20
  import { pipeline } from "node:stream/promises";
21
21
  import open from "open";
@@ -301,6 +301,35 @@ const packageIRSchema = z.object({
301
301
  visibility: z.enum(["public", "private"]).optional(),
302
302
  audit: z.object({ min_score: z.number().min(0).max(10) }).strict().optional()
303
303
  }).strict();
304
+ const commandSchema$1 = z.string().min(1, "command must not be empty");
305
+ const argSchema$1 = z.array(z.string()).default([]);
306
+ const envSchema$1 = z.record(z.string(), z.string()).optional();
307
+ const remoteUrlSchema$1 = z.string().url("remote must be a valid URL");
308
+ const localMcpServerSchema = z.object({
309
+ command: commandSchema$1,
310
+ args: argSchema$1,
311
+ env: envSchema$1,
312
+ requires_auth: z.literal(false).optional()
313
+ }).strict();
314
+ const remoteMcpServerSchema = z.object({
315
+ remote: remoteUrlSchema$1,
316
+ requires_auth: z.boolean().default(false),
317
+ env: envSchema$1
318
+ }).strict();
319
+ const mcpServerSchema$1 = z.union([localMcpServerSchema, remoteMcpServerSchema]);
320
+ function isRemoteMcpServer(server) {
321
+ return "remote" in server;
322
+ }
323
+ const perToolOverrideSchema$1 = z.object({
324
+ scan: z.boolean().optional(),
325
+ blockOnMatch: z.boolean().optional()
326
+ }).strict();
327
+ z.object({
328
+ perfBudgetMs: z.number().positive().optional(),
329
+ blockOnMatch: z.boolean().optional(),
330
+ resetPinsOnMismatch: z.boolean().optional(),
331
+ perTool: z.record(z.string(), perToolOverrideSchema$1).optional()
332
+ }).strict();
304
333
  const baseManifestFields$1 = {
305
334
  name: z.string().min(1, "Name must not be empty").max(214, `Name must be 214 characters or fewer`).regex(/^@[a-z0-9-]+\/[a-z0-9][a-z0-9-]*$/, "Name must be scoped (@org/name), lowercase alphanumeric and hyphens"),
306
335
  version: z.string().regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/, "Version must be valid semver"),
@@ -309,7 +338,8 @@ const baseManifestFields$1 = {
309
338
  permissions: permissionsSchema$1.optional(),
310
339
  repository: z.string().url("Repository must be a valid URL").optional(),
311
340
  visibility: z.enum(["public", "private"]).optional(),
312
- audit: z.object({ min_score: z.number().min(0).max(10) }).strict().optional()
341
+ audit: z.object({ min_score: z.number().min(0).max(10) }).strict().optional(),
342
+ mcp_server: mcpServerSchema$1.optional()
313
343
  };
314
344
  /** Legacy skills.json schema — strict, no atoms. Used for backward-compatible consumers. */
315
345
  const skillsJsonSchema = z.object(baseManifestFields$1).strict();
@@ -467,7 +497,7 @@ function scoreColor$3(score) {
467
497
  if (score >= 4) return chalk.yellow;
468
498
  return chalk.red;
469
499
  }
470
- function formatScore(result) {
500
+ function formatScore$1(result) {
471
501
  if (result.error) return chalk.dim("error");
472
502
  if (result.score == null || result.status !== "completed") return chalk.dim("pending");
473
503
  return scoreColor$3(result.score)(result.score.toFixed(1));
@@ -510,7 +540,7 @@ function displayDetailedAudit(result) {
510
540
  console.log(chalk.bold(result.name));
511
541
  console.log("");
512
542
  console.log(`${chalk.dim("Version:".padEnd(14))}${result.version}`);
513
- console.log(`${chalk.dim("Audit Score:".padEnd(14))}${formatScore(result)}`);
543
+ console.log(`${chalk.dim("Audit Score:".padEnd(14))}${formatScore$1(result)}`);
514
544
  console.log(`${chalk.dim("Status:".padEnd(14))}${result.status}`);
515
545
  const perms = result.permissions;
516
546
  if (perms) {
@@ -534,7 +564,7 @@ function displayTable(results) {
534
564
  for (const result of results) {
535
565
  const name = chalk.bold(padRight$1(result.name, 30));
536
566
  const version = padRight$1(result.version, 12);
537
- const score = padRight$1(formatScore(result), 10);
567
+ const score = padRight$1(formatScore$1(result), 10);
538
568
  const status = formatStatus(result);
539
569
  console.log(`${name}${version}${score}${status}`);
540
570
  }
@@ -2705,6 +2735,8 @@ async function initCommand(options = {}) {
2705
2735
  }
2706
2736
  //#endregion
2707
2737
  //#region ../internals-helpers/dist/index.js
2738
+ const ALPHANUMERIC$1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2739
+ `${ALPHANUMERIC$1}`, `${ALPHANUMERIC$1}`, `${ALPHANUMERIC$1}`, `${ALPHANUMERIC$1}`;
2708
2740
  function resolve$1(range, versions) {
2709
2741
  try {
2710
2742
  if (!range || !semver.validRange(range)) return null;
@@ -2716,6 +2748,196 @@ function resolve$1(range, versions) {
2716
2748
  }
2717
2749
  }
2718
2750
  //#endregion
2751
+ //#region src/lib/adapter-rewriter.ts
2752
+ const DEFAULT_TANK_BINARY = "tank";
2753
+ const AUTH_ENV_VAR_PREFIX = "TANK_MCP_AUTH_";
2754
+ const AUTH_PLACEHOLDER_VALUE = "<agent-config-resolves-this>";
2755
+ function deriveAuthEnvVarName(skillName) {
2756
+ const lastSlash = skillName.lastIndexOf("/");
2757
+ return `${AUTH_ENV_VAR_PREFIX}${(lastSlash === -1 ? skillName : skillName.slice(lastSlash + 1)).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase()}`;
2758
+ }
2759
+ function passthroughEntry(server) {
2760
+ if (isRemoteMcpServer(server)) throw new Error("adapter-rewriter: remote MCP servers cannot opt out (proxy transport is mandatory for remote)");
2761
+ const entry = {
2762
+ command: server.command,
2763
+ args: [...server.args]
2764
+ };
2765
+ if (server.env) entry.env = { ...server.env };
2766
+ return entry;
2767
+ }
2768
+ function wrapLocal(server, binary) {
2769
+ const entry = {
2770
+ command: binary,
2771
+ args: [
2772
+ "proxy",
2773
+ "--",
2774
+ server.command,
2775
+ ...server.args
2776
+ ]
2777
+ };
2778
+ if (server.env) entry.env = { ...server.env };
2779
+ return entry;
2780
+ }
2781
+ function wrapRemote(skillName, server, binary) {
2782
+ const entry = {
2783
+ command: binary,
2784
+ args: [
2785
+ "proxy",
2786
+ "--remote",
2787
+ server.remote
2788
+ ]
2789
+ };
2790
+ const mergedEnv = { ...server.env ?? {} };
2791
+ if (server.requires_auth) mergedEnv[deriveAuthEnvVarName(skillName)] = AUTH_PLACEHOLDER_VALUE;
2792
+ if (Object.keys(mergedEnv).length > 0) entry.env = mergedEnv;
2793
+ return entry;
2794
+ }
2795
+ function rewriteMcpServerEntry(options) {
2796
+ const binary = options.tankBinaryPath ?? DEFAULT_TANK_BINARY;
2797
+ if (options.dangerouslyNoTankProxy) return passthroughEntry(options.mcpServer);
2798
+ if (isRemoteMcpServer(options.mcpServer)) return wrapRemote(options.skillName, options.mcpServer, binary);
2799
+ return wrapLocal(options.mcpServer, binary);
2800
+ }
2801
+ //#endregion
2802
+ //#region src/lib/mcp-config-writer.ts
2803
+ const AGENT_CONFIG_PATHS = {
2804
+ claude: [".claude", "settings.json"],
2805
+ cursor: [".cursor", "mcp.json"],
2806
+ opencode: [
2807
+ ".config",
2808
+ "opencode",
2809
+ "mcp.json"
2810
+ ],
2811
+ codex: [".codex", "config.json"],
2812
+ openclaw: [".openclaw", "mcp.json"],
2813
+ universal: [
2814
+ ".config",
2815
+ "mcp",
2816
+ "servers.json"
2817
+ ]
2818
+ };
2819
+ function getAgentConfigPath(agentId, homedir) {
2820
+ const segments = AGENT_CONFIG_PATHS[agentId];
2821
+ if (!segments) return null;
2822
+ const base = homedir ?? os.homedir();
2823
+ return path.join(base, ...segments);
2824
+ }
2825
+ function readConfigOrEmpty(configPath) {
2826
+ if (!fs.existsSync(configPath)) return {};
2827
+ try {
2828
+ const raw = fs.readFileSync(configPath, "utf-8");
2829
+ const parsed = JSON.parse(raw);
2830
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
2831
+ return parsed;
2832
+ } catch {
2833
+ return {};
2834
+ }
2835
+ }
2836
+ function persistConfig(configPath, config) {
2837
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
2838
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
2839
+ }
2840
+ function writeMcpServerEntry(options) {
2841
+ const configPath = getAgentConfigPath(options.agentId, options.homedir);
2842
+ if (!configPath) throw new Error(`unknown agent: ${options.agentId}`);
2843
+ const existing = readConfigOrEmpty(configPath);
2844
+ const mcpServers = { ...existing.mcpServers ?? {} };
2845
+ if (options.remove) {
2846
+ if (!(options.skillName in mcpServers)) {
2847
+ if (!fs.existsSync(configPath)) return;
2848
+ }
2849
+ delete mcpServers[options.skillName];
2850
+ } else {
2851
+ if (!options.entry) throw new Error("writeMcpServerEntry: entry is required when remove=false");
2852
+ mcpServers[options.skillName] = options.entry;
2853
+ }
2854
+ persistConfig(configPath, {
2855
+ ...existing,
2856
+ mcpServers
2857
+ });
2858
+ }
2859
+ //#endregion
2860
+ //#region src/lib/apply-proxy-wrapping.ts
2861
+ function readManifest(skillDir) {
2862
+ const manifestPath = path.join(skillDir, "tank.json");
2863
+ if (!fs.existsSync(manifestPath)) return "missing";
2864
+ try {
2865
+ const raw = fs.readFileSync(manifestPath, "utf-8");
2866
+ const parsed = JSON.parse(raw);
2867
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return "invalid";
2868
+ return parsed;
2869
+ } catch {
2870
+ return "invalid";
2871
+ }
2872
+ }
2873
+ function warnOptOut(skillName) {
2874
+ console.warn(`Proxy disabled for ${skillName} — MCP traffic will not be scanned`);
2875
+ }
2876
+ function isKnownAgent(agentId) {
2877
+ return agentId in AGENT_CONFIG_PATHS;
2878
+ }
2879
+ function applyRemoval(options) {
2880
+ const wrapped = [];
2881
+ for (const agentId of options.agentIds) {
2882
+ if (!isKnownAgent(agentId)) continue;
2883
+ writeMcpServerEntry({
2884
+ agentId,
2885
+ skillName: options.skillName,
2886
+ remove: true,
2887
+ homedir: options.homedir
2888
+ });
2889
+ wrapped.push(agentId);
2890
+ }
2891
+ return {
2892
+ wrapped,
2893
+ skipped: []
2894
+ };
2895
+ }
2896
+ function applyProxyWrapping(options) {
2897
+ if (options.remove) return applyRemoval(options);
2898
+ const manifest = readManifest(options.skillDir);
2899
+ if (manifest === "missing") return {
2900
+ wrapped: [],
2901
+ skipped: ["no-manifest"]
2902
+ };
2903
+ if (manifest === "invalid") return {
2904
+ wrapped: [],
2905
+ skipped: ["invalid-manifest"]
2906
+ };
2907
+ const rawMcpServer = manifest.mcp_server;
2908
+ if (rawMcpServer === void 0) return {
2909
+ wrapped: [],
2910
+ skipped: ["no-mcp-server"]
2911
+ };
2912
+ const parseResult = mcpServerSchema$1.safeParse(rawMcpServer);
2913
+ if (!parseResult.success) return {
2914
+ wrapped: [],
2915
+ skipped: ["invalid-mcp-server"]
2916
+ };
2917
+ const entry = rewriteMcpServerEntry({
2918
+ skillName: options.skillName,
2919
+ mcpServer: parseResult.data,
2920
+ ...options.tankBinaryPath !== void 0 ? { tankBinaryPath: options.tankBinaryPath } : {},
2921
+ ...options.dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
2922
+ });
2923
+ if (options.dangerouslyNoTankProxy) warnOptOut(options.skillName);
2924
+ const wrapped = [];
2925
+ for (const agentId of options.agentIds) {
2926
+ if (!isKnownAgent(agentId)) continue;
2927
+ writeMcpServerEntry({
2928
+ agentId,
2929
+ skillName: options.skillName,
2930
+ entry,
2931
+ homedir: options.homedir
2932
+ });
2933
+ wrapped.push(agentId);
2934
+ }
2935
+ return {
2936
+ wrapped,
2937
+ skipped: []
2938
+ };
2939
+ }
2940
+ //#endregion
2719
2941
  //#region ../sdk/dist/index.mjs
2720
2942
  var __create = Object.create;
2721
2943
  var __defProp = Object.defineProperty;
@@ -6913,6 +7135,30 @@ object({
6913
7135
  visibility: _enum(["public", "private"]).optional(),
6914
7136
  audit: object({ min_score: number().min(0).max(10) }).strict().optional()
6915
7137
  }).strict();
7138
+ const commandSchema = string().min(1, "command must not be empty");
7139
+ const argSchema = array(string()).default([]);
7140
+ const envSchema = record(string(), string()).optional();
7141
+ const remoteUrlSchema = string().url("remote must be a valid URL");
7142
+ const mcpServerSchema = union([object({
7143
+ command: commandSchema,
7144
+ args: argSchema,
7145
+ env: envSchema,
7146
+ requires_auth: literal(false).optional()
7147
+ }).strict(), object({
7148
+ remote: remoteUrlSchema,
7149
+ requires_auth: boolean().default(false),
7150
+ env: envSchema
7151
+ }).strict()]);
7152
+ const perToolOverrideSchema = object({
7153
+ scan: boolean().optional(),
7154
+ blockOnMatch: boolean().optional()
7155
+ }).strict();
7156
+ object({
7157
+ perfBudgetMs: number().positive().optional(),
7158
+ blockOnMatch: boolean().optional(),
7159
+ resetPinsOnMismatch: boolean().optional(),
7160
+ perTool: record(string(), perToolOverrideSchema).optional()
7161
+ }).strict();
6916
7162
  const baseManifestFields = {
6917
7163
  name: string().min(1, "Name must not be empty").max(214, `Name must be 214 characters or fewer`).regex(/^@[a-z0-9-]+\/[a-z0-9][a-z0-9-]*$/, "Name must be scoped (@org/name), lowercase alphanumeric and hyphens"),
6918
7164
  version: string().regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/, "Version must be valid semver"),
@@ -6921,7 +7167,8 @@ const baseManifestFields = {
6921
7167
  permissions: permissionsSchema.optional(),
6922
7168
  repository: string().url("Repository must be a valid URL").optional(),
6923
7169
  visibility: _enum(["public", "private"]).optional(),
6924
- audit: object({ min_score: number().min(0).max(10) }).strict().optional()
7170
+ audit: object({ min_score: number().min(0).max(10) }).strict().optional(),
7171
+ mcp_server: mcpServerSchema.optional()
6925
7172
  };
6926
7173
  object(baseManifestFields).strict();
6927
7174
  object({
@@ -7019,47 +7266,6 @@ var TankIntegrityError = class extends TankError {
7019
7266
  }
7020
7267
  };
7021
7268
  createRequire(import.meta.url);
7022
- function checkPermissionBudget(budget, skillPerms, skillName) {
7023
- if (!skillPerms) return;
7024
- if (skillPerms.subprocess === true && budget.subprocess !== true) throw new TankPermissionError(`${skillName} requires subprocess access, but project budget does not allow it`);
7025
- if (skillPerms.network?.outbound && skillPerms.network.outbound.length > 0) {
7026
- const budgetDomains = budget.network?.outbound ?? [];
7027
- for (const domain of skillPerms.network.outbound) if (!isDomainAllowed$1(domain, budgetDomains)) throw new TankPermissionError(`${skillName} requests network access to "${domain}", which is not in the project's permission budget`);
7028
- }
7029
- if (skillPerms.filesystem?.read && skillPerms.filesystem.read.length > 0) {
7030
- const budgetPaths = budget.filesystem?.read ?? [];
7031
- for (const p of skillPerms.filesystem.read) if (!isPathAllowed$1(p, budgetPaths)) throw new TankPermissionError(`${skillName} requests filesystem read access to "${p}", which is not in the project's permission budget`);
7032
- }
7033
- if (skillPerms.filesystem?.write && skillPerms.filesystem.write.length > 0) {
7034
- const budgetPaths = budget.filesystem?.write ?? [];
7035
- for (const p of skillPerms.filesystem.write) if (!isPathAllowed$1(p, budgetPaths)) throw new TankPermissionError(`${skillName} requests filesystem write access to "${p}", which is not in the project's permission budget`);
7036
- }
7037
- }
7038
- function isDomainAllowed$1(domain, allowedDomains) {
7039
- for (const allowed of allowedDomains) {
7040
- if (allowed === domain) return true;
7041
- if (allowed.startsWith("*.")) {
7042
- const suffix = allowed.slice(1);
7043
- if (domain.endsWith(suffix) || domain === allowed.slice(2)) return true;
7044
- if (domain === allowed) return true;
7045
- }
7046
- }
7047
- return false;
7048
- }
7049
- function isPathAllowed$1(requestedPath, allowedPaths) {
7050
- const norm = (p) => p.replaceAll("\\", "/");
7051
- const req = norm(requestedPath);
7052
- if (req.includes("..")) return false;
7053
- for (const allowed of allowedPaths) {
7054
- const a = norm(allowed);
7055
- if (a === req) return true;
7056
- if (a.endsWith("/**")) {
7057
- const prefix = a.slice(0, -3);
7058
- if (req === prefix || req.startsWith(`${prefix}/`)) return true;
7059
- }
7060
- }
7061
- return false;
7062
- }
7063
7269
  var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7064
7270
  const SEMVER_SPEC_VERSION = "2.0.0";
7065
7271
  const MAX_LENGTH = 256;
@@ -8307,6 +8513,8 @@ var import_semver = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
8307
8513
  rcompareIdentifiers: identifiers.rcompareIdentifiers
8308
8514
  };
8309
8515
  })))(), 1);
8516
+ const ALPHANUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
8517
+ `${ALPHANUMERIC}`, `${ALPHANUMERIC}`, `${ALPHANUMERIC}`, `${ALPHANUMERIC}`;
8310
8518
  function resolve(range, versions) {
8311
8519
  try {
8312
8520
  if (!range || !import_semver.default.validRange(range)) return null;
@@ -8317,6 +8525,68 @@ function resolve(range, versions) {
8317
8525
  return null;
8318
8526
  }
8319
8527
  }
8528
+ function isDomainAllowed$1(domain, allowedDomains) {
8529
+ for (const allowed of allowedDomains) {
8530
+ if (allowed === domain) return true;
8531
+ if (allowed.startsWith("*.")) {
8532
+ const suffix = allowed.slice(1);
8533
+ if (domain.endsWith(suffix) || domain === allowed.slice(2)) return true;
8534
+ if (domain === allowed) return true;
8535
+ }
8536
+ }
8537
+ return false;
8538
+ }
8539
+ function isPathAllowed$1(requestedPath, allowedPaths) {
8540
+ const norm = (p) => p.replaceAll("\\", "/");
8541
+ const req = norm(requestedPath);
8542
+ if (req.includes("..")) return false;
8543
+ for (const allowed of allowedPaths) {
8544
+ const a = norm(allowed);
8545
+ if (a === req) return true;
8546
+ if (a.endsWith("/**")) {
8547
+ const prefix = a.slice(0, -3);
8548
+ if (req === prefix || req.startsWith(`${prefix}/`)) return true;
8549
+ }
8550
+ }
8551
+ return false;
8552
+ }
8553
+ /**
8554
+ * Thrown by checkPermissionBudget when a skill's declared permissions exceed the project budget.
8555
+ *
8556
+ * internals-helpers cannot import from @tankpkg/sdk (would invert dep graph).
8557
+ * sdk shim catches this and re-throws as TankPermissionError to preserve
8558
+ * the public sdk error API. See D7 / INTENT C25b.
8559
+ */
8560
+ var PermissionBudgetError = class extends Error {
8561
+ constructor(message) {
8562
+ super(message);
8563
+ this.name = "PermissionBudgetError";
8564
+ }
8565
+ };
8566
+ function checkPermissionBudget$1(budget, skillPerms, skillName) {
8567
+ if (!skillPerms) return;
8568
+ if (skillPerms.subprocess === true && budget.subprocess !== true) throw new PermissionBudgetError(`${skillName} requires subprocess access, but project budget does not allow it`);
8569
+ if (skillPerms.network?.outbound && skillPerms.network.outbound.length > 0) {
8570
+ const budgetDomains = budget.network?.outbound ?? [];
8571
+ for (const domain of skillPerms.network.outbound) if (!isDomainAllowed$1(domain, budgetDomains)) throw new PermissionBudgetError(`${skillName} requests network access to "${domain}", which is not in the project's permission budget`);
8572
+ }
8573
+ if (skillPerms.filesystem?.read && skillPerms.filesystem.read.length > 0) {
8574
+ const budgetPaths = budget.filesystem?.read ?? [];
8575
+ for (const p of skillPerms.filesystem.read) if (!isPathAllowed$1(p, budgetPaths)) throw new PermissionBudgetError(`${skillName} requests filesystem read access to "${p}", which is not in the project's permission budget`);
8576
+ }
8577
+ if (skillPerms.filesystem?.write && skillPerms.filesystem.write.length > 0) {
8578
+ const budgetPaths = budget.filesystem?.write ?? [];
8579
+ for (const p of skillPerms.filesystem.write) if (!isPathAllowed$1(p, budgetPaths)) throw new PermissionBudgetError(`${skillName} requests filesystem write access to "${p}", which is not in the project's permission budget`);
8580
+ }
8581
+ }
8582
+ function checkPermissionBudget(budget, skillPerms, skillName) {
8583
+ try {
8584
+ checkPermissionBudget$1(budget, skillPerms, skillName);
8585
+ } catch (e) {
8586
+ if (e instanceof PermissionBudgetError) throw new TankPermissionError(e.message);
8587
+ throw e;
8588
+ }
8589
+ }
8320
8590
  function buildSkillKey(name, version) {
8321
8591
  return `${name}@${version}`;
8322
8592
  }
@@ -8937,12 +9207,13 @@ const HOST_MAP = [
8937
9207
  [/registry\.npmjs\.org/i, "npm"]
8938
9208
  ];
8939
9209
  function detectSourceType(url) {
9210
+ if (url.startsWith("file://")) return "file";
8940
9211
  for (const [pattern, sourceType] of HOST_MAP) if (pattern.test(url)) return sourceType;
8941
9212
  return "unknown";
8942
9213
  }
8943
9214
  /** Returns true if the input looks like a URL rather than a package name. */
8944
9215
  function isUrl(input) {
8945
- if (input.startsWith("http://") || input.startsWith("https://")) return true;
9216
+ if (input.startsWith("http://") || input.startsWith("https://") || input.startsWith("file://")) return true;
8946
9217
  for (const [pattern] of HOST_MAP) if (pattern.test(input)) return true;
8947
9218
  return false;
8948
9219
  }
@@ -9226,6 +9497,24 @@ async function fetchFromGenericUrl(url, tempDir) {
9226
9497
  cleanup: () => cleanupDir(tempDir)
9227
9498
  };
9228
9499
  }
9500
+ async function fetchFromFileUrl(url, tempDir) {
9501
+ const sourcePath = new URL(url).pathname;
9502
+ const destDir = join(tempDir, "skill");
9503
+ const { cp } = await import("node:fs/promises");
9504
+ await mkdir(destDir, { recursive: true });
9505
+ await cp(sourcePath, destDir, {
9506
+ recursive: true,
9507
+ errorOnExist: false
9508
+ });
9509
+ return {
9510
+ localPath: destDir,
9511
+ sourceType: "file",
9512
+ sourceUrl: url,
9513
+ commitSha: null,
9514
+ inferredName: sourcePath.replace(/\/$/, "").split("/").pop() ?? null,
9515
+ cleanup: () => cleanupDir(tempDir)
9516
+ };
9517
+ }
9229
9518
  /** Fetch a skill from a URL to a local temp directory. */
9230
9519
  async function fetchFromUrl(url) {
9231
9520
  const sourceType = detectSourceType(url);
@@ -9243,6 +9532,9 @@ async function fetchFromUrl(url) {
9243
9532
  case "skills_sh":
9244
9533
  result = await fetchFromSkillsSh(url, tempDir);
9245
9534
  break;
9535
+ case "file":
9536
+ result = await fetchFromFileUrl(url, tempDir);
9537
+ break;
9246
9538
  default:
9247
9539
  result = await fetchFromGenericUrl(url, tempDir);
9248
9540
  break;
@@ -9397,8 +9689,28 @@ function installToolDependencies(extractDir, skillName) {
9397
9689
  logger.warn(`Dependency install skipped for ${skillName} (non-fatal)`);
9398
9690
  }
9399
9691
  }
9692
+ function wrapMcpServerForSkill(options) {
9693
+ try {
9694
+ const agents = detectInstalledAgents(options.homedir);
9695
+ if (agents.length === 0) return;
9696
+ const result = applyProxyWrapping({
9697
+ skillName: options.skillName,
9698
+ skillDir: options.extractDir,
9699
+ agentIds: agents.map((a) => a.id),
9700
+ ...options.homedir !== void 0 ? { homedir: options.homedir } : {},
9701
+ ...options.dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
9702
+ });
9703
+ if (result.wrapped.length > 0) {
9704
+ const mode = options.dangerouslyNoTankProxy ? "registered (proxy disabled)" : "proxy-wrapped";
9705
+ logger.info(`${options.skillName}: ${mode} in ${result.wrapped.length} agent config(s)`);
9706
+ }
9707
+ } catch (err) {
9708
+ const msg = err instanceof Error ? err.message : String(err);
9709
+ logger.warn(`MCP proxy wrapping skipped for ${options.skillName}: ${msg}`);
9710
+ }
9711
+ }
9400
9712
  async function linkInstalledRoots(options) {
9401
- const { rootSkillNames, resolvedNodeByName, extractDirForSkill, directory, global, resolvedHome, homedir } = options;
9713
+ const { rootSkillNames, resolvedNodeByName, extractDirForSkill, directory, global, resolvedHome, homedir, dangerouslyNoTankProxy } = options;
9402
9714
  const agentSkillsBaseDir = global ? getGlobalAgentSkillsDir(resolvedHome) : path.join(directory, ".tank", "agent-skills");
9403
9715
  const linksDir = global ? path.join(resolvedHome, ".tank") : path.join(directory, ".tank");
9404
9716
  for (const skillName of rootSkillNames) try {
@@ -9418,6 +9730,12 @@ async function linkInstalledRoots(options) {
9418
9730
  });
9419
9731
  if (linkResult.linked.length > 0) logger.info(`Linked to ${linkResult.linked.length} agent(s)`);
9420
9732
  if (linkResult.failed.length > 0) for (const failedLink of linkResult.failed) logger.warn(`Failed to link to ${failedLink.agentId}: ${failedLink.error}`);
9733
+ wrapMcpServerForSkill({
9734
+ skillName,
9735
+ extractDir: extractDirForSkill(skillName),
9736
+ homedir,
9737
+ dangerouslyNoTankProxy
9738
+ });
9421
9739
  } catch {
9422
9740
  if (rootSkillNames.length === 1) logger.warn("Agent linking skipped (non-fatal)");
9423
9741
  else logger.warn(`Agent linking skipped for ${skillName} (non-fatal)`);
@@ -9490,12 +9808,13 @@ async function executeInstallPipeline(options) {
9490
9808
  directory,
9491
9809
  global,
9492
9810
  resolvedHome,
9493
- homedir
9811
+ homedir,
9812
+ ...options.dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
9494
9813
  });
9495
9814
  return updatedLock;
9496
9815
  }
9497
9816
  async function installCommand(options) {
9498
- const { name, versionRange = "*", directory = process.cwd(), configDir, global = false, homedir, isTransitive = false } = options;
9817
+ const { name, versionRange = "*", directory = process.cwd(), configDir, global = false, homedir, isTransitive = false, dangerouslyNoTankProxy } = options;
9499
9818
  const config = getConfig(configDir);
9500
9819
  const resolvedHome = homedir ?? os.homedir();
9501
9820
  const requestHeaders = { "User-Agent": USER_AGENT };
@@ -9551,7 +9870,8 @@ async function installCommand(options) {
9551
9870
  rootSkillNames: [name],
9552
9871
  projectPermissions,
9553
9872
  auditMinScore,
9554
- spinner
9873
+ spinner,
9874
+ ...dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
9555
9875
  });
9556
9876
  if (!global && !isTransitive) {
9557
9877
  const skills = skillsJson.skills ?? {};
@@ -9566,7 +9886,7 @@ async function installCommand(options) {
9566
9886
  }
9567
9887
  }
9568
9888
  async function installFromLockfile(options) {
9569
- const { directory = process.cwd(), configDir, global = false, homedir } = options;
9889
+ const { directory = process.cwd(), configDir, global = false, homedir, dangerouslyNoTankProxy } = options;
9570
9890
  const resolvedHome = homedir ?? os.homedir();
9571
9891
  const config = getConfig(configDir);
9572
9892
  const requestHeaders = { "User-Agent": USER_AGENT };
@@ -9593,42 +9913,46 @@ async function installFromLockfile(options) {
9593
9913
  const skillName = parseLockKey$2(key);
9594
9914
  const version = parseVersionFromLockKey(key);
9595
9915
  spinner.text = `Installing ${key}...`;
9596
- const encodedName = encodeURIComponent(skillName);
9597
- const metaUrl = `${config.registry}/api/v1/skills/${encodedName}/${version}`;
9598
- let metaRes;
9599
- try {
9600
- metaRes = await fetch(metaUrl, { headers: requestHeaders });
9601
- } catch (err) {
9602
- throw new Error(`Network error fetching ${key}: ${err instanceof Error ? err.message : String(err)}`);
9603
- }
9604
- if (!metaRes.ok) {
9605
- if (metaRes.status === 404) throw new Error(`Skill or version not found: ${key}`);
9606
- const body = await metaRes.json().catch(() => null);
9607
- throw new Error(`Failed to fetch ${key}: ${body?.error ?? metaRes.statusText}`);
9608
- }
9609
- const downloadUrl = (await metaRes.json()).downloadUrl;
9610
- const downloadRes = await fetch(downloadUrl);
9611
- if (!downloadRes.ok) throw new Error(`Failed to download ${key}: ${downloadRes.status} ${downloadRes.statusText}`);
9612
- const tarballBuffer = Buffer.from(await downloadRes.arrayBuffer());
9613
- const computedIntegrity = buildIntegrity(tarballBuffer);
9614
- if (computedIntegrity !== entry.integrity) throw new Error(`Integrity mismatch for ${key}. Expected: ${entry.integrity}, Got: ${computedIntegrity}`);
9615
9916
  const extractDir = global ? getGlobalExtractDir(resolvedHome, skillName) : getExtractDir$1(directory, skillName);
9616
- if (fs.existsSync(extractDir)) fs.rmSync(extractDir, {
9617
- recursive: true,
9618
- force: true
9619
- });
9620
- fs.mkdirSync(extractDir, { recursive: true });
9621
- await extractSafely(tarballBuffer, extractDir);
9622
- if (global) try {
9917
+ if (!fs.existsSync(path.join(extractDir, "tank.json"))) {
9918
+ const encodedName = encodeURIComponent(skillName);
9919
+ const metaUrl = `${config.registry}/api/v1/skills/${encodedName}/${version}`;
9920
+ let metaRes;
9921
+ try {
9922
+ metaRes = await fetch(metaUrl, { headers: requestHeaders });
9923
+ } catch (err) {
9924
+ throw new Error(`Network error fetching ${key}: ${err instanceof Error ? err.message : String(err)}`);
9925
+ }
9926
+ if (!metaRes.ok) {
9927
+ if (metaRes.status === 404) throw new Error(`Skill or version not found: ${key}`);
9928
+ const body = await metaRes.json().catch(() => null);
9929
+ throw new Error(`Failed to fetch ${key}: ${body?.error ?? metaRes.statusText}`);
9930
+ }
9931
+ const downloadUrl = (await metaRes.json()).downloadUrl;
9932
+ const downloadRes = await fetch(downloadUrl);
9933
+ if (!downloadRes.ok) throw new Error(`Failed to download ${key}: ${downloadRes.status} ${downloadRes.statusText}`);
9934
+ const tarballBuffer = Buffer.from(await downloadRes.arrayBuffer());
9935
+ const computedIntegrity = buildIntegrity(tarballBuffer);
9936
+ if (computedIntegrity !== entry.integrity) throw new Error(`Integrity mismatch for ${key}. Expected: ${entry.integrity}, Got: ${computedIntegrity}`);
9937
+ if (fs.existsSync(extractDir)) fs.rmSync(extractDir, {
9938
+ recursive: true,
9939
+ force: true
9940
+ });
9941
+ fs.mkdirSync(extractDir, { recursive: true });
9942
+ await extractSafely(tarballBuffer, extractDir);
9943
+ }
9944
+ try {
9945
+ const agentSkillsBaseDir = global ? getGlobalAgentSkillsDir(resolvedHome) : path.join(directory, ".tank", "agent-skills");
9946
+ const linksDir = global ? path.join(resolvedHome, ".tank") : path.join(directory, ".tank");
9623
9947
  const linkResult = linkSkillToAgents({
9624
9948
  skillName,
9625
9949
  sourceDir: prepareAgentSkillDir({
9626
9950
  skillName,
9627
9951
  extractDir,
9628
- agentSkillsBaseDir: getGlobalAgentSkillsDir(resolvedHome)
9952
+ agentSkillsBaseDir
9629
9953
  }),
9630
- linksDir: path.join(resolvedHome, ".tank"),
9631
- source: "global",
9954
+ linksDir,
9955
+ source: global ? "global" : "local",
9632
9956
  homedir
9633
9957
  });
9634
9958
  if (detectInstalledAgents(homedir).length === 0) logger.warn("No agents detected for linking");
@@ -9637,6 +9961,12 @@ async function installFromLockfile(options) {
9637
9961
  } catch {
9638
9962
  logger.warn("Agent linking skipped (non-fatal)");
9639
9963
  }
9964
+ wrapMcpServerForSkill({
9965
+ skillName,
9966
+ extractDir,
9967
+ ...homedir !== void 0 ? { homedir } : {},
9968
+ ...dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
9969
+ });
9640
9970
  }
9641
9971
  spinner.succeed(`Installed ${entries.length} skill${entries.length === 1 ? "" : "s"} from lockfile`);
9642
9972
  } catch (err) {
@@ -9649,7 +9979,7 @@ async function installFromLockfile(options) {
9649
9979
  }
9650
9980
  }
9651
9981
  async function installAll(options) {
9652
- const { directory = process.cwd(), configDir, global = false, homedir } = options;
9982
+ const { directory = process.cwd(), configDir, global = false, homedir, dangerouslyNoTankProxy } = options;
9653
9983
  const resolvedHome = homedir ?? os.homedir();
9654
9984
  const config = getConfig(configDir);
9655
9985
  const requestHeaders = { "User-Agent": USER_AGENT };
@@ -9662,7 +9992,8 @@ async function installAll(options) {
9662
9992
  directory,
9663
9993
  configDir,
9664
9994
  global,
9665
- homedir
9995
+ homedir,
9996
+ ...dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
9666
9997
  });
9667
9998
  if (global) {
9668
9999
  logger.info(`No ${LOCKFILE_FILENAME} found — nothing to install`);
@@ -9704,7 +10035,8 @@ async function installAll(options) {
9704
10035
  rootSkillNames: skillEntries.map(([skillName]) => skillName),
9705
10036
  projectPermissions,
9706
10037
  auditMinScore,
9707
- spinner
10038
+ spinner,
10039
+ ...dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
9708
10040
  });
9709
10041
  spinner.succeed(`Installed ${skillEntries.length} root skill${skillEntries.length === 1 ? "" : "s"}`);
9710
10042
  } catch (err) {
@@ -9757,7 +10089,7 @@ function readManifestFromDir(dir) {
9757
10089
  return null;
9758
10090
  }
9759
10091
  async function installFromUrl(url, options) {
9760
- const { global = false, yes = false } = options;
10092
+ const { global = false, yes = false, dangerouslyNoTankProxy } = options;
9761
10093
  const resolvedHome = os.homedir();
9762
10094
  const directory = process.cwd();
9763
10095
  const spinner = ora(`Fetching from URL...`).start();
@@ -9778,13 +10110,16 @@ async function installFromUrl(url, options) {
9778
10110
  process.exit(1);
9779
10111
  }
9780
10112
  try {
9781
- const scanResult = await scanUrl(url);
9782
- displayScanResults(scanResult);
9783
- const enforcement = await enforceVerdict(scanResult, { yes });
9784
- if (!enforcement.allowed) {
9785
- spinner.fail(enforcement.reason ?? "Install blocked by security scan");
9786
- await fetchResult.cleanup();
9787
- process.exit(1);
10113
+ let scanResult = null;
10114
+ if (!url.startsWith("file://")) {
10115
+ scanResult = await scanUrl(url);
10116
+ displayScanResults(scanResult);
10117
+ const enforcement = await enforceVerdict(scanResult, { yes });
10118
+ if (!enforcement.allowed) {
10119
+ spinner.fail(enforcement.reason ?? "Install blocked by security scan");
10120
+ await fetchResult.cleanup();
10121
+ process.exit(1);
10122
+ }
9788
10123
  }
9789
10124
  const skillMdPath = path.join(fetchResult.localPath, "SKILL.md");
9790
10125
  if (!fs.existsSync(skillMdPath)) throw new Error("No SKILL.md found. This doesn't appear to be a valid skill.");
@@ -9816,12 +10151,12 @@ async function installFromUrl(url, options) {
9816
10151
  const lockKey = `${skillName}@${skillVersion}`;
9817
10152
  const skillPermissions = existingManifest?.permissions ?? {};
9818
10153
  lock.skills[lockKey] = {
9819
- resolved: url.startsWith("http") ? url : `https://${url}`,
10154
+ resolved: url.startsWith("http") ? url : url.startsWith("file://") ? url : `https://${url}`,
9820
10155
  integrity,
9821
10156
  permissions: skillPermissions,
9822
- audit_score: scanResult.auditScore ?? null,
10157
+ audit_score: scanResult?.auditScore ?? null,
9823
10158
  source: mapSourceType(fetchResult.sourceType),
9824
- scan_verdict: scanResult.verdict,
10159
+ scan_verdict: scanResult?.verdict ?? "pass",
9825
10160
  scanned_at: (/* @__PURE__ */ new Date()).toISOString()
9826
10161
  };
9827
10162
  lock.lockfileVersion = 2;
@@ -9848,6 +10183,11 @@ async function installFromUrl(url, options) {
9848
10183
  logger.warn("Agent linking skipped (non-fatal)");
9849
10184
  }
9850
10185
  if (detectInstalledAgents().length === 0) logger.warn("No agents detected for linking");
10186
+ wrapMcpServerForSkill({
10187
+ skillName,
10188
+ extractDir: installDir,
10189
+ ...dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
10190
+ });
9851
10191
  await fetchResult.cleanup();
9852
10192
  spinner.succeed(`Installed ${skillName} from ${fetchResult.sourceType}`);
9853
10193
  if (linkedAgents.length > 0) logger.info(`Linked to ${linkedAgents.join(", ")}`);
@@ -10211,6 +10551,30 @@ async function permissionsCommand(options) {
10211
10551
  }
10212
10552
  }
10213
10553
  //#endregion
10554
+ //#region src/commands/proxy.ts
10555
+ const PROXY_MODULE = "@tankpkg/proxy";
10556
+ async function proxyCommand(options) {
10557
+ if (!options.command || options.command.length === 0) throw new Error("tank proxy: missing child command (usage: tank proxy -- <command> [args...])");
10558
+ const { startProxy } = await import(PROXY_MODULE);
10559
+ if (options.verbose) console.error(chalk.dim(`[tank proxy] spawning: ${options.command} ${options.args.join(" ")}`));
10560
+ const startOptions = {
10561
+ command: options.command,
10562
+ args: options.args
10563
+ };
10564
+ if (options.auditPath !== void 0) startOptions.auditPath = options.auditPath;
10565
+ if (options.enableMl === true) startOptions.enableMl = true;
10566
+ const handle = await startProxy(startOptions);
10567
+ const forwardSignal = (signal) => {
10568
+ try {
10569
+ handle.kill(signal);
10570
+ } catch {}
10571
+ };
10572
+ process.on("SIGINT", () => forwardSignal("SIGINT"));
10573
+ process.on("SIGTERM", () => forwardSignal("SIGTERM"));
10574
+ const code = await handle.exitCode;
10575
+ process.exit(code);
10576
+ }
10577
+ //#endregion
10214
10578
  //#region src/lib/packer.ts
10215
10579
  const MAX_PACKAGE_SIZE = 50 * 1024 * 1024;
10216
10580
  const MAX_FILE_COUNT = 1e3;
@@ -10892,6 +11256,11 @@ function scoreColor(score) {
10892
11256
  if (score >= 4) return chalk.yellow;
10893
11257
  return chalk.red;
10894
11258
  }
11259
+ function formatScore(score) {
11260
+ if (score == null || !Number.isFinite(score)) return chalk.dim(padRight("N/A", 8));
11261
+ const scoreStr = Number.isInteger(score) ? score.toFixed(1) : String(score);
11262
+ return scoreColor(score)(padRight(scoreStr, 8));
11263
+ }
10895
11264
  function truncate(text, maxLen) {
10896
11265
  if (text.length <= maxLen) return text;
10897
11266
  return `${text.slice(0, maxLen - 3)}...`;
@@ -10924,9 +11293,8 @@ async function searchCommand(options) {
10924
11293
  console.log(`${padRight("NAME", 30) + padRight("VERSION", 10) + padRight("SCORE", 8)}DESCRIPTION`);
10925
11294
  for (const result of data.results) {
10926
11295
  const name = chalk.bold(padRight(result.name, 30));
10927
- const version = padRight(result.latestVersion, 10);
10928
- const scoreStr = Number.isInteger(result.auditScore) ? result.auditScore.toFixed(1) : String(result.auditScore);
10929
- const score = scoreColor(result.auditScore)(padRight(scoreStr, 8));
11296
+ const version = padRight(result.latestVersion ?? "-", 10);
11297
+ const score = formatScore(result.auditScore);
10930
11298
  const desc = truncate(result.description ?? "", MAX_DESC_LENGTH);
10931
11299
  console.log(`${name}${version}${score}${desc}`);
10932
11300
  }
@@ -11509,18 +11877,23 @@ program.command("publish").alias("pub").description("Pack and publish a skill to
11509
11877
  process.exit(1);
11510
11878
  }
11511
11879
  });
11512
- program.command("install").alias("i").description("Install a skill from the Tank registry, a URL, or all skills from lockfile").argument("[name]", "Skill name or URL (e.g., @org/skill-name or https://github.com/owner/repo). Omit to install from lockfile.").argument("[version-range]", "Semver range (default: *)", "*").option("-g, --global", "Install skill globally (available to all projects)").option("-y, --yes", "Auto-accept flagged scan verdicts").action(async (name, versionRange, opts) => {
11880
+ program.command("install").alias("i").description("Install a skill from the Tank registry, a URL, or all skills from lockfile").argument("[name]", "Skill name or URL (e.g., @org/skill-name or https://github.com/owner/repo). Omit to install from lockfile.").argument("[version-range]", "Semver range (default: *)", "*").option("-g, --global", "Install skill globally (available to all projects)").option("-y, --yes", "Auto-accept flagged scan verdicts").option("--dangerously-no-tank-proxy", "Skip wrapping MCP servers with the tank proxy (no scanning, no enforcement)").action(async (name, versionRange, opts) => {
11513
11881
  try {
11514
11882
  if (name && isUrl(name)) await installFromUrl(name, {
11515
11883
  global: opts.global,
11516
- yes: opts.yes
11884
+ yes: opts.yes,
11885
+ ...opts.dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
11517
11886
  });
11518
11887
  else if (name) await installCommand({
11519
11888
  name,
11520
11889
  versionRange,
11521
- global: opts.global
11890
+ global: opts.global,
11891
+ ...opts.dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
11892
+ });
11893
+ else await installAll({
11894
+ global: opts.global,
11895
+ ...opts.dangerouslyNoTankProxy ? { dangerouslyNoTankProxy: true } : {}
11522
11896
  });
11523
- else await installAll({ global: opts.global });
11524
11897
  } catch (err) {
11525
11898
  const msg = err instanceof Error ? err.message : String(err);
11526
11899
  console.error(`Install failed: ${msg}`);
@@ -11610,6 +11983,42 @@ program.command("run").description("Launch an agent with credential protection (
11610
11983
  process.exit(1);
11611
11984
  }
11612
11985
  });
11986
+ program.command("proxy").description("Transparent MCP proxy — wraps an MCP server with runtime enforcement").argument("[command]", "Child MCP server command to wrap (omit when using --reset-pins, --remote, or download-ml-model)").allowUnknownOption(true).allowExcessArguments(true).option("--audit-path <path>", "JSONL audit log path (default: ~/.tank/proxy/audit.jsonl)").option("--reset-pins", "Delete all rug-pull schema pins under ~/.tank/proxy/pins/ and continue").option("--remote <url>", "Connect to a remote MCP server over SSE/HTTP instead of spawning a child").option("--requires-auth", "Require TANK_MCP_AUTH_<SLUG> env var before connecting to the remote").option("--enable-ml", "Enable the opt-in ML-based prompt-injection classifier (requires ~500MB model; run `tank proxy download-ml-model` first)").option("--verbose", "Print proxy diagnostic details to stderr").action(async (command, opts, cmd) => {
11987
+ try {
11988
+ if (command === "download-ml-model") {
11989
+ const { proxyDownloadMlCommand } = await import("../proxy-download-ml-DUk7ehNs.js");
11990
+ const downloadOpts = {};
11991
+ if (process.argv.includes("--yes") || process.argv.includes("-y")) downloadOpts.yes = true;
11992
+ await proxyDownloadMlCommand(downloadOpts);
11993
+ return;
11994
+ }
11995
+ if (opts.resetPins) {
11996
+ const { proxyResetPinsCommand } = await import("../proxy-reset-pins-CfEbaL-F.js");
11997
+ proxyResetPinsCommand();
11998
+ }
11999
+ if (opts.remote) {
12000
+ const { proxyRemoteCommand } = await import("../proxy-remote-CCemokkz.js");
12001
+ await proxyRemoteCommand({
12002
+ url: opts.remote,
12003
+ requiresAuth: opts.requiresAuth === true
12004
+ });
12005
+ return;
12006
+ }
12007
+ if (!command) return;
12008
+ const proxyOpts = {
12009
+ command,
12010
+ args: cmd.args.slice(1)
12011
+ };
12012
+ if (opts.auditPath !== void 0) proxyOpts.auditPath = opts.auditPath;
12013
+ if (opts.verbose !== void 0) proxyOpts.verbose = opts.verbose;
12014
+ if (opts.enableMl === true) proxyOpts.enableMl = true;
12015
+ await proxyCommand(proxyOpts);
12016
+ } catch (err) {
12017
+ const msg = err instanceof Error ? err.message : String(err);
12018
+ console.error(`Proxy failed: ${msg}`);
12019
+ process.exit(1);
12020
+ }
12021
+ });
11613
12022
  program.command("scan").description("Scan a local skill for security issues without publishing").option("-d, --directory <path>", "Directory to scan (default: current directory)").action(async (opts) => {
11614
12023
  try {
11615
12024
  await scanCommand({ directory: opts.directory });