@tankpkg/cli 0.15.6 → 0.15.8
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 +252 -121
- package/dist/bin/tank.js.map +1 -1
- package/dist/{debug-logger-BsbdLGGp.js → debug-logger-BCwL85ni.js} +2 -2
- package/dist/{debug-logger-BsbdLGGp.js.map → debug-logger-BCwL85ni.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/package.json +11 -1
- package/package.json +14 -2
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-
|
|
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-BCwL85ni.js";
|
|
3
3
|
import { t as logger } from "../logger-BhULz3Uz.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { Command } from "commander";
|
|
@@ -231,6 +231,54 @@ z.enum([
|
|
|
231
231
|
"org.member.remove",
|
|
232
232
|
"org.delete"
|
|
233
233
|
]);
|
|
234
|
+
const commandSchema$1 = z.string().min(1, "command must not be empty");
|
|
235
|
+
const argSchema$1 = z.array(z.string()).default([]);
|
|
236
|
+
const envSchema$1 = z.record(z.string(), z.string()).optional();
|
|
237
|
+
const remoteUrlSchema$1 = z.string().url("remote must be a valid URL");
|
|
238
|
+
const localMcpServerSchema = z.object({
|
|
239
|
+
command: commandSchema$1,
|
|
240
|
+
args: argSchema$1,
|
|
241
|
+
env: envSchema$1,
|
|
242
|
+
requires_auth: z.literal(false).optional()
|
|
243
|
+
}).strict();
|
|
244
|
+
const remoteMcpServerSchema = z.object({
|
|
245
|
+
remote: remoteUrlSchema$1,
|
|
246
|
+
requires_auth: z.boolean().default(false),
|
|
247
|
+
env: envSchema$1
|
|
248
|
+
}).strict();
|
|
249
|
+
const mcpServerSchema$1 = z.union([localMcpServerSchema, remoteMcpServerSchema]);
|
|
250
|
+
function isRemoteMcpServer(server) {
|
|
251
|
+
return "remote" in server;
|
|
252
|
+
}
|
|
253
|
+
const baseManifestFields$1 = {
|
|
254
|
+
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"),
|
|
255
|
+
version: z.string().regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/, "Version must be valid semver"),
|
|
256
|
+
description: z.string().max(500, `Description must be 500 characters or fewer`).optional(),
|
|
257
|
+
skills: z.record(z.string(), z.string()).optional(),
|
|
258
|
+
permissions: permissionsSchema$1.optional(),
|
|
259
|
+
repository: z.string().url("Repository must be a valid URL").optional(),
|
|
260
|
+
visibility: z.enum(["public", "private"]).optional(),
|
|
261
|
+
audit: z.object({ min_score: z.number().min(0).max(10) }).strict().optional(),
|
|
262
|
+
mcp_server: mcpServerSchema$1.optional()
|
|
263
|
+
};
|
|
264
|
+
/** Legacy skills.json schema — strict, no atoms. Used for backward-compatible consumers. */
|
|
265
|
+
const skillsJsonSchema = z.object(baseManifestFields$1).strict();
|
|
266
|
+
const publishConfigSchema$1 = z.object({
|
|
267
|
+
build: z.string().min(1, "publish.build must be a non-empty shell command").optional(),
|
|
268
|
+
files: z.array(z.string().min(1)).optional()
|
|
269
|
+
}).strict();
|
|
270
|
+
/**
|
|
271
|
+
* Publish manifest schema — accepts both legacy skills.json AND atom-enriched tank.json.
|
|
272
|
+
* The `atoms` and `includes` fields are passed through as opaque JSON arrays,
|
|
273
|
+
* validated only at surface level. Full atom IR validation happens at build time.
|
|
274
|
+
* The `publish` block is a CLI-only lifecycle config (build hook + files allow-list).
|
|
275
|
+
*/
|
|
276
|
+
const publishManifestSchema = z.object({
|
|
277
|
+
...baseManifestFields$1,
|
|
278
|
+
atoms: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
279
|
+
includes: z.array(z.string()).optional(),
|
|
280
|
+
publish: publishConfigSchema$1.optional()
|
|
281
|
+
}).strict();
|
|
234
282
|
const promptIRSchema$1 = z.object({
|
|
235
283
|
kind: z.literal("prompt"),
|
|
236
284
|
name: z.string().min(1, "Prompt name must not be empty"),
|
|
@@ -269,8 +317,9 @@ const mcpServerConfigSchema$1 = z.object({
|
|
|
269
317
|
args: z.array(z.string()).optional(),
|
|
270
318
|
env: z.record(z.string(), z.string()).optional(),
|
|
271
319
|
runtime: z.string().min(1).optional(),
|
|
272
|
-
entry: z.string().min(1).optional()
|
|
273
|
-
|
|
320
|
+
entry: z.string().min(1).optional(),
|
|
321
|
+
package: z.string().min(1).optional()
|
|
322
|
+
}).strict().refine((data) => Boolean(data.command) || Boolean(data.runtime && (data.entry || data.package)), "MCP config must have either \"command\" or \"runtime\" plus one of \"entry\"/\"package\"");
|
|
274
323
|
const toolIRSchema$1 = z.object({
|
|
275
324
|
kind: z.literal("tool"),
|
|
276
325
|
name: z.string().min(1, "Tool name must not be empty"),
|
|
@@ -299,27 +348,9 @@ const packageIRSchema = z.object({
|
|
|
299
348
|
permissions: permissionsSchema$1.optional(),
|
|
300
349
|
repository: z.string().url("Repository must be a valid URL").optional(),
|
|
301
350
|
visibility: z.enum(["public", "private"]).optional(),
|
|
302
|
-
audit: z.object({ min_score: z.number().min(0).max(10) }).strict().optional()
|
|
303
|
-
|
|
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
|
|
351
|
+
audit: z.object({ min_score: z.number().min(0).max(10) }).strict().optional(),
|
|
352
|
+
publish: publishConfigSchema$1.optional()
|
|
318
353
|
}).strict();
|
|
319
|
-
const mcpServerSchema$1 = z.union([localMcpServerSchema, remoteMcpServerSchema]);
|
|
320
|
-
function isRemoteMcpServer(server) {
|
|
321
|
-
return "remote" in server;
|
|
322
|
-
}
|
|
323
354
|
const perToolOverrideSchema$1 = z.object({
|
|
324
355
|
scan: z.boolean().optional(),
|
|
325
356
|
blockOnMatch: z.boolean().optional()
|
|
@@ -330,29 +361,6 @@ z.object({
|
|
|
330
361
|
resetPinsOnMismatch: z.boolean().optional(),
|
|
331
362
|
perTool: z.record(z.string(), perToolOverrideSchema$1).optional()
|
|
332
363
|
}).strict();
|
|
333
|
-
const baseManifestFields$1 = {
|
|
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"),
|
|
335
|
-
version: z.string().regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/, "Version must be valid semver"),
|
|
336
|
-
description: z.string().max(500, `Description must be 500 characters or fewer`).optional(),
|
|
337
|
-
skills: z.record(z.string(), z.string()).optional(),
|
|
338
|
-
permissions: permissionsSchema$1.optional(),
|
|
339
|
-
repository: z.string().url("Repository must be a valid URL").optional(),
|
|
340
|
-
visibility: z.enum(["public", "private"]).optional(),
|
|
341
|
-
audit: z.object({ min_score: z.number().min(0).max(10) }).strict().optional(),
|
|
342
|
-
mcp_server: mcpServerSchema$1.optional()
|
|
343
|
-
};
|
|
344
|
-
/** Legacy skills.json schema — strict, no atoms. Used for backward-compatible consumers. */
|
|
345
|
-
const skillsJsonSchema = z.object(baseManifestFields$1).strict();
|
|
346
|
-
/**
|
|
347
|
-
* Publish manifest schema — accepts both legacy skills.json AND atom-enriched tank.json.
|
|
348
|
-
* The `atoms` and `includes` fields are passed through as opaque JSON arrays,
|
|
349
|
-
* validated only at surface level. Full atom IR validation happens at build time.
|
|
350
|
-
*/
|
|
351
|
-
const publishManifestSchema = z.object({
|
|
352
|
-
...baseManifestFields$1,
|
|
353
|
-
atoms: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
354
|
-
includes: z.array(z.string()).optional()
|
|
355
|
-
}).strict();
|
|
356
364
|
const SKILL_SOURCES$1 = [
|
|
357
365
|
"registry",
|
|
358
366
|
"github",
|
|
@@ -633,6 +641,89 @@ async function auditCommand(options) {
|
|
|
633
641
|
}
|
|
634
642
|
//#endregion
|
|
635
643
|
//#region ../adapters/dist/index.mjs
|
|
644
|
+
function resolveMcpCommand(atom, adapterName) {
|
|
645
|
+
if (atom.mcp) return resolveFromMcpBlock(atom.mcp);
|
|
646
|
+
const fromExtensions = resolveFromExtensions(atom.extensions, adapterName);
|
|
647
|
+
if (fromExtensions) return fromExtensions;
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
function resolveFromMcpBlock(mcp) {
|
|
651
|
+
const env = mcp.env;
|
|
652
|
+
if (mcp.command) return {
|
|
653
|
+
command: mcp.command,
|
|
654
|
+
args: mcp.args ?? [],
|
|
655
|
+
...env ? { env } : {}
|
|
656
|
+
};
|
|
657
|
+
if (!mcp.runtime) return null;
|
|
658
|
+
const args = mcp.args ?? [];
|
|
659
|
+
switch (mcp.runtime) {
|
|
660
|
+
case "uvx":
|
|
661
|
+
if (!mcp.package) return null;
|
|
662
|
+
return {
|
|
663
|
+
command: "uvx",
|
|
664
|
+
args: [mcp.package, ...args],
|
|
665
|
+
...env ? { env } : {}
|
|
666
|
+
};
|
|
667
|
+
case "npx":
|
|
668
|
+
if (!mcp.package) return null;
|
|
669
|
+
return {
|
|
670
|
+
command: "npx",
|
|
671
|
+
args: [
|
|
672
|
+
"-y",
|
|
673
|
+
mcp.package,
|
|
674
|
+
...args
|
|
675
|
+
],
|
|
676
|
+
...env ? { env } : {}
|
|
677
|
+
};
|
|
678
|
+
case "bunx":
|
|
679
|
+
if (!mcp.package) return null;
|
|
680
|
+
return {
|
|
681
|
+
command: "bunx",
|
|
682
|
+
args: [mcp.package, ...args],
|
|
683
|
+
...env ? { env } : {}
|
|
684
|
+
};
|
|
685
|
+
case "pipx":
|
|
686
|
+
if (!mcp.package) return null;
|
|
687
|
+
return {
|
|
688
|
+
command: "pipx",
|
|
689
|
+
args: [
|
|
690
|
+
"run",
|
|
691
|
+
mcp.package,
|
|
692
|
+
...args
|
|
693
|
+
],
|
|
694
|
+
...env ? { env } : {}
|
|
695
|
+
};
|
|
696
|
+
case "node":
|
|
697
|
+
if (!mcp.entry) return null;
|
|
698
|
+
return {
|
|
699
|
+
command: "node",
|
|
700
|
+
args: [mcp.entry, ...args],
|
|
701
|
+
...env ? { env } : {}
|
|
702
|
+
};
|
|
703
|
+
case "python":
|
|
704
|
+
if (!mcp.entry) return null;
|
|
705
|
+
return {
|
|
706
|
+
command: "python",
|
|
707
|
+
args: [mcp.entry, ...args],
|
|
708
|
+
...env ? { env } : {}
|
|
709
|
+
};
|
|
710
|
+
default: return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function resolveFromExtensions(extensions, adapterName) {
|
|
714
|
+
if (!extensions) return null;
|
|
715
|
+
const bag = extensions[adapterName];
|
|
716
|
+
if (!bag || typeof bag !== "object") return null;
|
|
717
|
+
const candidate = bag;
|
|
718
|
+
if (typeof candidate.command !== "string" || candidate.command.length === 0) return null;
|
|
719
|
+
const args = Array.isArray(candidate.args) ? candidate.args.filter((a) => typeof a === "string") : [];
|
|
720
|
+
const env = candidate.env && typeof candidate.env === "object" && !Array.isArray(candidate.env) ? Object.fromEntries(Object.entries(candidate.env).filter(([, v]) => typeof v === "string")) : void 0;
|
|
721
|
+
return {
|
|
722
|
+
command: candidate.command,
|
|
723
|
+
args,
|
|
724
|
+
...env ? { env } : {}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
636
727
|
function emitInstruction$5(atom) {
|
|
637
728
|
const globs = atom.globs?.length ? atom.globs : void 0;
|
|
638
729
|
if (globs) {
|
|
@@ -733,7 +824,8 @@ function emitAgent$5(atom) {
|
|
|
733
824
|
};
|
|
734
825
|
}
|
|
735
826
|
function emitTool$5(atom) {
|
|
736
|
-
|
|
827
|
+
const resolved = resolveMcpCommand(atom, "claude-code");
|
|
828
|
+
if (!resolved) return {
|
|
737
829
|
files: [],
|
|
738
830
|
warnings: [{
|
|
739
831
|
level: "skipped",
|
|
@@ -742,9 +834,9 @@ function emitTool$5(atom) {
|
|
|
742
834
|
}]
|
|
743
835
|
};
|
|
744
836
|
const mcpConfig = { mcpServers: { [atom.name]: {
|
|
745
|
-
command:
|
|
746
|
-
args:
|
|
747
|
-
...
|
|
837
|
+
command: resolved.command,
|
|
838
|
+
args: resolved.args,
|
|
839
|
+
...resolved.env ? { env: resolved.env } : {}
|
|
748
840
|
} } };
|
|
749
841
|
return {
|
|
750
842
|
files: [{
|
|
@@ -944,7 +1036,8 @@ function emitAgent$4(atom) {
|
|
|
944
1036
|
};
|
|
945
1037
|
}
|
|
946
1038
|
function emitTool$4(atom) {
|
|
947
|
-
|
|
1039
|
+
const resolved = resolveMcpCommand(atom, "cline");
|
|
1040
|
+
if (!resolved) return {
|
|
948
1041
|
files: [],
|
|
949
1042
|
warnings: [{
|
|
950
1043
|
level: "skipped",
|
|
@@ -953,10 +1046,10 @@ function emitTool$4(atom) {
|
|
|
953
1046
|
}]
|
|
954
1047
|
};
|
|
955
1048
|
const config = { mcpServers: { [atom.name]: {
|
|
956
|
-
command:
|
|
957
|
-
args:
|
|
1049
|
+
command: resolved.command,
|
|
1050
|
+
args: resolved.args,
|
|
958
1051
|
disabled: false,
|
|
959
|
-
...
|
|
1052
|
+
...resolved.env ? { env: resolved.env } : {}
|
|
960
1053
|
} } };
|
|
961
1054
|
return {
|
|
962
1055
|
files: [{
|
|
@@ -1126,7 +1219,8 @@ function emitAgent$3(atom) {
|
|
|
1126
1219
|
};
|
|
1127
1220
|
}
|
|
1128
1221
|
function emitTool$3(atom) {
|
|
1129
|
-
|
|
1222
|
+
const resolved = resolveMcpCommand(atom, "cursor");
|
|
1223
|
+
if (!resolved) return {
|
|
1130
1224
|
files: [],
|
|
1131
1225
|
warnings: [{
|
|
1132
1226
|
level: "skipped",
|
|
@@ -1135,9 +1229,9 @@ function emitTool$3(atom) {
|
|
|
1135
1229
|
}]
|
|
1136
1230
|
};
|
|
1137
1231
|
const config = { mcpServers: { [atom.name]: {
|
|
1138
|
-
command:
|
|
1139
|
-
args:
|
|
1140
|
-
...
|
|
1232
|
+
command: resolved.command,
|
|
1233
|
+
args: resolved.args,
|
|
1234
|
+
...resolved.env ? { env: resolved.env } : {}
|
|
1141
1235
|
} } };
|
|
1142
1236
|
return {
|
|
1143
1237
|
files: [{
|
|
@@ -1324,7 +1418,8 @@ function emitAgent$2(atom) {
|
|
|
1324
1418
|
};
|
|
1325
1419
|
}
|
|
1326
1420
|
function emitTool$2(atom) {
|
|
1327
|
-
|
|
1421
|
+
const resolved = resolveMcpCommand(atom, "opencode");
|
|
1422
|
+
if (!resolved) return {
|
|
1328
1423
|
files: [],
|
|
1329
1424
|
warnings: [{
|
|
1330
1425
|
level: "skipped",
|
|
@@ -1334,8 +1429,8 @@ function emitTool$2(atom) {
|
|
|
1334
1429
|
};
|
|
1335
1430
|
const config = { [atom.name]: {
|
|
1336
1431
|
type: "local",
|
|
1337
|
-
command: [
|
|
1338
|
-
...
|
|
1432
|
+
command: [resolved.command, ...resolved.args],
|
|
1433
|
+
...resolved.env ? { environment: resolved.env } : {}
|
|
1339
1434
|
} };
|
|
1340
1435
|
return {
|
|
1341
1436
|
files: [{
|
|
@@ -1598,7 +1693,8 @@ function emitAgent$1(atom) {
|
|
|
1598
1693
|
};
|
|
1599
1694
|
}
|
|
1600
1695
|
function emitTool$1(atom) {
|
|
1601
|
-
|
|
1696
|
+
const resolved = resolveMcpCommand(atom, "roo-code");
|
|
1697
|
+
if (!resolved) return {
|
|
1602
1698
|
files: [],
|
|
1603
1699
|
warnings: [{
|
|
1604
1700
|
level: "skipped",
|
|
@@ -1607,10 +1703,10 @@ function emitTool$1(atom) {
|
|
|
1607
1703
|
}]
|
|
1608
1704
|
};
|
|
1609
1705
|
const config = { mcpServers: { [atom.name]: {
|
|
1610
|
-
command:
|
|
1611
|
-
args:
|
|
1706
|
+
command: resolved.command,
|
|
1707
|
+
args: resolved.args,
|
|
1612
1708
|
disabled: false,
|
|
1613
|
-
...
|
|
1709
|
+
...resolved.env ? { env: resolved.env } : {}
|
|
1614
1710
|
} } };
|
|
1615
1711
|
return {
|
|
1616
1712
|
files: [{
|
|
@@ -1768,7 +1864,8 @@ function emitAgent(atom) {
|
|
|
1768
1864
|
};
|
|
1769
1865
|
}
|
|
1770
1866
|
function emitTool(atom) {
|
|
1771
|
-
|
|
1867
|
+
const resolved = resolveMcpCommand(atom, "windsurf");
|
|
1868
|
+
if (!resolved) return {
|
|
1772
1869
|
files: [],
|
|
1773
1870
|
warnings: [{
|
|
1774
1871
|
level: "skipped",
|
|
@@ -1777,9 +1874,9 @@ function emitTool(atom) {
|
|
|
1777
1874
|
}]
|
|
1778
1875
|
};
|
|
1779
1876
|
const config = { mcpServers: { [atom.name]: {
|
|
1780
|
-
command:
|
|
1781
|
-
args:
|
|
1782
|
-
...
|
|
1877
|
+
command: resolved.command,
|
|
1878
|
+
args: resolved.args,
|
|
1879
|
+
...resolved.env ? { env: resolved.env } : {}
|
|
1783
1880
|
} } };
|
|
1784
1881
|
return {
|
|
1785
1882
|
files: [{
|
|
@@ -7065,6 +7162,42 @@ _enum([
|
|
|
7065
7162
|
"org.member.remove",
|
|
7066
7163
|
"org.delete"
|
|
7067
7164
|
]);
|
|
7165
|
+
const commandSchema = string().min(1, "command must not be empty");
|
|
7166
|
+
const argSchema = array(string()).default([]);
|
|
7167
|
+
const envSchema = record(string(), string()).optional();
|
|
7168
|
+
const remoteUrlSchema = string().url("remote must be a valid URL");
|
|
7169
|
+
const mcpServerSchema = union([object({
|
|
7170
|
+
command: commandSchema,
|
|
7171
|
+
args: argSchema,
|
|
7172
|
+
env: envSchema,
|
|
7173
|
+
requires_auth: literal(false).optional()
|
|
7174
|
+
}).strict(), object({
|
|
7175
|
+
remote: remoteUrlSchema,
|
|
7176
|
+
requires_auth: boolean().default(false),
|
|
7177
|
+
env: envSchema
|
|
7178
|
+
}).strict()]);
|
|
7179
|
+
const baseManifestFields = {
|
|
7180
|
+
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"),
|
|
7181
|
+
version: string().regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/, "Version must be valid semver"),
|
|
7182
|
+
description: string().max(500, `Description must be 500 characters or fewer`).optional(),
|
|
7183
|
+
skills: record(string(), string()).optional(),
|
|
7184
|
+
permissions: permissionsSchema.optional(),
|
|
7185
|
+
repository: string().url("Repository must be a valid URL").optional(),
|
|
7186
|
+
visibility: _enum(["public", "private"]).optional(),
|
|
7187
|
+
audit: object({ min_score: number().min(0).max(10) }).strict().optional(),
|
|
7188
|
+
mcp_server: mcpServerSchema.optional()
|
|
7189
|
+
};
|
|
7190
|
+
object(baseManifestFields).strict();
|
|
7191
|
+
const publishConfigSchema = object({
|
|
7192
|
+
build: string().min(1, "publish.build must be a non-empty shell command").optional(),
|
|
7193
|
+
files: array(string().min(1)).optional()
|
|
7194
|
+
}).strict();
|
|
7195
|
+
object({
|
|
7196
|
+
...baseManifestFields,
|
|
7197
|
+
atoms: array(record(string(), unknown())).optional(),
|
|
7198
|
+
includes: array(string()).optional(),
|
|
7199
|
+
publish: publishConfigSchema.optional()
|
|
7200
|
+
}).strict();
|
|
7068
7201
|
const promptIRSchema = object({
|
|
7069
7202
|
kind: literal("prompt"),
|
|
7070
7203
|
name: string().min(1, "Prompt name must not be empty"),
|
|
@@ -7103,8 +7236,9 @@ const mcpServerConfigSchema = object({
|
|
|
7103
7236
|
args: array(string()).optional(),
|
|
7104
7237
|
env: record(string(), string()).optional(),
|
|
7105
7238
|
runtime: string().min(1).optional(),
|
|
7106
|
-
entry: string().min(1).optional()
|
|
7107
|
-
|
|
7239
|
+
entry: string().min(1).optional(),
|
|
7240
|
+
package: string().min(1).optional()
|
|
7241
|
+
}).strict().refine((data) => Boolean(data.command) || Boolean(data.runtime && (data.entry || data.package)), "MCP config must have either \"command\" or \"runtime\" plus one of \"entry\"/\"package\"");
|
|
7108
7242
|
const toolIRSchema = object({
|
|
7109
7243
|
kind: literal("tool"),
|
|
7110
7244
|
name: string().min(1, "Tool name must not be empty"),
|
|
@@ -7133,22 +7267,9 @@ object({
|
|
|
7133
7267
|
permissions: permissionsSchema.optional(),
|
|
7134
7268
|
repository: string().url("Repository must be a valid URL").optional(),
|
|
7135
7269
|
visibility: _enum(["public", "private"]).optional(),
|
|
7136
|
-
audit: object({ min_score: number().min(0).max(10) }).strict().optional()
|
|
7270
|
+
audit: object({ min_score: number().min(0).max(10) }).strict().optional(),
|
|
7271
|
+
publish: publishConfigSchema.optional()
|
|
7137
7272
|
}).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
7273
|
const perToolOverrideSchema = object({
|
|
7153
7274
|
scan: boolean().optional(),
|
|
7154
7275
|
blockOnMatch: boolean().optional()
|
|
@@ -7159,23 +7280,6 @@ object({
|
|
|
7159
7280
|
resetPinsOnMismatch: boolean().optional(),
|
|
7160
7281
|
perTool: record(string(), perToolOverrideSchema).optional()
|
|
7161
7282
|
}).strict();
|
|
7162
|
-
const baseManifestFields = {
|
|
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"),
|
|
7164
|
-
version: string().regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/, "Version must be valid semver"),
|
|
7165
|
-
description: string().max(500, `Description must be 500 characters or fewer`).optional(),
|
|
7166
|
-
skills: record(string(), string()).optional(),
|
|
7167
|
-
permissions: permissionsSchema.optional(),
|
|
7168
|
-
repository: string().url("Repository must be a valid URL").optional(),
|
|
7169
|
-
visibility: _enum(["public", "private"]).optional(),
|
|
7170
|
-
audit: object({ min_score: number().min(0).max(10) }).strict().optional(),
|
|
7171
|
-
mcp_server: mcpServerSchema.optional()
|
|
7172
|
-
};
|
|
7173
|
-
object(baseManifestFields).strict();
|
|
7174
|
-
object({
|
|
7175
|
-
...baseManifestFields,
|
|
7176
|
-
atoms: array(record(string(), unknown())).optional(),
|
|
7177
|
-
includes: array(string()).optional()
|
|
7178
|
-
}).strict();
|
|
7179
7283
|
const SKILL_SOURCES = [
|
|
7180
7284
|
"registry",
|
|
7181
7285
|
"github",
|
|
@@ -10607,6 +10711,31 @@ async function proxyCommand(options) {
|
|
|
10607
10711
|
process.exit(code);
|
|
10608
10712
|
}
|
|
10609
10713
|
//#endregion
|
|
10714
|
+
//#region src/lib/build-hook.ts
|
|
10715
|
+
function runBuildHook(directory, command) {
|
|
10716
|
+
return new Promise((resolve, reject) => {
|
|
10717
|
+
const child = spawn(command, {
|
|
10718
|
+
cwd: directory,
|
|
10719
|
+
shell: true,
|
|
10720
|
+
stdio: "inherit"
|
|
10721
|
+
});
|
|
10722
|
+
child.on("error", (err) => {
|
|
10723
|
+
reject(/* @__PURE__ */ new Error(`Build hook failed to start: ${err.message}`));
|
|
10724
|
+
});
|
|
10725
|
+
child.on("close", (code, signal) => {
|
|
10726
|
+
if (code === 0) {
|
|
10727
|
+
resolve();
|
|
10728
|
+
return;
|
|
10729
|
+
}
|
|
10730
|
+
if (signal) {
|
|
10731
|
+
reject(/* @__PURE__ */ new Error(`Build hook terminated by signal ${signal}`));
|
|
10732
|
+
return;
|
|
10733
|
+
}
|
|
10734
|
+
reject(/* @__PURE__ */ new Error(`Build hook exited with code ${code ?? "unknown"}`));
|
|
10735
|
+
});
|
|
10736
|
+
});
|
|
10737
|
+
}
|
|
10738
|
+
//#endregion
|
|
10610
10739
|
//#region src/lib/packer.ts
|
|
10611
10740
|
const MAX_PACKAGE_SIZE = 50 * 1024 * 1024;
|
|
10612
10741
|
const MAX_FILE_COUNT = 1e3;
|
|
@@ -10620,18 +10749,7 @@ const DEFAULT_IGNORES = [
|
|
|
10620
10749
|
];
|
|
10621
10750
|
const ALWAYS_IGNORED = ["node_modules", ".git"];
|
|
10622
10751
|
const IGNORE_FILES = [".tankignore", ".gitignore"];
|
|
10623
|
-
|
|
10624
|
-
* Pack a skill directory into a .tgz tarball with integrity hashing.
|
|
10625
|
-
*
|
|
10626
|
-
* Validates:
|
|
10627
|
-
* - tank.json (or skills.json) exists and is valid
|
|
10628
|
-
* - No symlinks or hardlinks
|
|
10629
|
-
* - No path traversal (.. components)
|
|
10630
|
-
* - No absolute paths
|
|
10631
|
-
* - File count <= 1000
|
|
10632
|
-
* - Tarball size <= 50MB
|
|
10633
|
-
*/
|
|
10634
|
-
async function pack(directory) {
|
|
10752
|
+
async function pack(directory, options = {}) {
|
|
10635
10753
|
const absDir = path.resolve(directory);
|
|
10636
10754
|
if (!fs.existsSync(absDir)) throw new Error(`Directory does not exist: ${absDir}`);
|
|
10637
10755
|
if (!fs.statSync(absDir).isDirectory()) throw new Error(`Not a directory: ${absDir}`);
|
|
@@ -10672,7 +10790,7 @@ async function pack(directory) {
|
|
|
10672
10790
|
} catch {
|
|
10673
10791
|
readmeContent = "";
|
|
10674
10792
|
}
|
|
10675
|
-
const files = collectFiles(absDir, absDir, buildIgnoreFilter(absDir));
|
|
10793
|
+
const files = options.files && options.files.length > 0 ? collectFromAllowList(absDir, options.files, manifestFilename) : collectFiles(absDir, absDir, buildIgnoreFilter(absDir));
|
|
10676
10794
|
if (files.length > MAX_FILE_COUNT) throw new Error(`Too many files: ${files.length} exceeds maximum of ${MAX_FILE_COUNT}`);
|
|
10677
10795
|
let totalSize = 0;
|
|
10678
10796
|
for (const file of files) {
|
|
@@ -10741,9 +10859,15 @@ async function packForScan(directory) {
|
|
|
10741
10859
|
files
|
|
10742
10860
|
};
|
|
10743
10861
|
}
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10862
|
+
function collectFromAllowList(baseDir, globs, manifestFilename) {
|
|
10863
|
+
const securityFilter = ignore().add(ALWAYS_IGNORED);
|
|
10864
|
+
const allowMatcher = ignore().add(globs);
|
|
10865
|
+
const all = collectFiles(baseDir, baseDir, securityFilter);
|
|
10866
|
+
const always = new Set([manifestFilename]);
|
|
10867
|
+
if (fs.existsSync(path.join(baseDir, "SKILL.md"))) always.add("SKILL.md");
|
|
10868
|
+
if (fs.existsSync(path.join(baseDir, "README.md"))) always.add("README.md");
|
|
10869
|
+
return all.filter((rel) => always.has(rel) || allowMatcher.ignores(rel));
|
|
10870
|
+
}
|
|
10747
10871
|
function buildIgnoreFilter(dir) {
|
|
10748
10872
|
const ig = ignore();
|
|
10749
10873
|
ig.add(ALWAYS_IGNORED);
|
|
@@ -10848,14 +10972,21 @@ async function publishCommand(options = {}) {
|
|
|
10848
10972
|
if (effectiveVisibility) manifest.visibility = effectiveVisibility;
|
|
10849
10973
|
const name = manifest.name;
|
|
10850
10974
|
const version = manifest.version;
|
|
10975
|
+
const publishConfig = manifest.publish ?? void 0;
|
|
10976
|
+
if (publishConfig?.build) {
|
|
10977
|
+
logger.info(`Running build: ${publishConfig.build}`);
|
|
10978
|
+
await runBuildHook(directory, publishConfig.build);
|
|
10979
|
+
}
|
|
10851
10980
|
const spinner = ora("Packing...").start();
|
|
10852
10981
|
let packResult;
|
|
10853
10982
|
try {
|
|
10854
|
-
packResult = await pack(directory);
|
|
10983
|
+
packResult = await pack(directory, publishConfig?.files ? { files: publishConfig.files } : {});
|
|
10855
10984
|
} catch (err) {
|
|
10856
10985
|
spinner.fail("Packing failed");
|
|
10857
10986
|
throw err;
|
|
10858
10987
|
}
|
|
10988
|
+
const outboundManifest = { ...manifest };
|
|
10989
|
+
delete outboundManifest.publish;
|
|
10859
10990
|
const { tarball, integrity, fileCount, totalSize, readme, files } = packResult;
|
|
10860
10991
|
if (dryRun) {
|
|
10861
10992
|
spinner.stop();
|
|
@@ -10891,7 +11022,7 @@ async function publishCommand(options = {}) {
|
|
|
10891
11022
|
method: "POST",
|
|
10892
11023
|
headers,
|
|
10893
11024
|
body: JSON.stringify({
|
|
10894
|
-
manifest,
|
|
11025
|
+
manifest: outboundManifest,
|
|
10895
11026
|
readme,
|
|
10896
11027
|
files
|
|
10897
11028
|
})
|