@tankpkg/cli 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/bin/tank.d.ts +2 -0
- package/dist/bin/tank.js +279 -0
- package/dist/bin/tank.js.map +1 -0
- package/dist/commands/audit.d.ts +5 -0
- package/dist/commands/audit.js +185 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +164 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/info.d.ts +5 -0
- package/dist/commands/info.js +102 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +92 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.d.ts +39 -0
- package/dist/commands/install.js +550 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/link.d.ts +5 -0
- package/dist/commands/link.js +79 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.js +87 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +9 -0
- package/dist/commands/logout.js +20 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/permissions.d.ts +4 -0
- package/dist/commands/permissions.js +199 -0
- package/dist/commands/permissions.js.map +1 -0
- package/dist/commands/publish.d.ts +25 -0
- package/dist/commands/publish.js +166 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/remove.d.ts +7 -0
- package/dist/commands/remove.js +163 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/search.d.ts +5 -0
- package/dist/commands/search.js +67 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/unlink.d.ts +5 -0
- package/dist/commands/unlink.js +42 -0
- package/dist/commands/unlink.js.map +1 -0
- package/dist/commands/update.d.ts +8 -0
- package/dist/commands/update.js +337 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/upgrade.d.ts +6 -0
- package/dist/commands/upgrade.js +100 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/verify.d.ts +22 -0
- package/dist/commands/verify.js +63 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.js +57 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/agents.d.ts +19 -0
- package/dist/lib/agents.js +84 -0
- package/dist/lib/agents.js.map +1 -0
- package/dist/lib/api-client.d.ts +14 -0
- package/dist/lib/api-client.js +63 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/config.d.ts +29 -0
- package/dist/lib/config.js +66 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/debug-logger.d.ts +9 -0
- package/dist/lib/debug-logger.js +77 -0
- package/dist/lib/debug-logger.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +11 -0
- package/dist/lib/frontmatter.js +89 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/linker.d.ts +45 -0
- package/dist/lib/linker.js +137 -0
- package/dist/lib/linker.js.map +1 -0
- package/dist/lib/links.d.ts +20 -0
- package/dist/lib/links.js +105 -0
- package/dist/lib/links.js.map +1 -0
- package/dist/lib/lockfile.d.ts +24 -0
- package/dist/lib/lockfile.js +135 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/logger.d.ts +6 -0
- package/dist/lib/logger.js +8 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/packer.d.ts +21 -0
- package/dist/lib/packer.js +210 -0
- package/dist/lib/packer.js.map +1 -0
- package/dist/lib/upgrade-check.d.ts +1 -0
- package/dist/lib/upgrade-check.js +52 -0
- package/dist/lib/upgrade-check.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Read and parse the lockfile from the given directory.
|
|
5
|
+
* Returns null if the file doesn't exist or is corrupt.
|
|
6
|
+
*/
|
|
7
|
+
export function readLockfile(directory) {
|
|
8
|
+
const dir = directory ?? process.cwd();
|
|
9
|
+
const lockPath = path.join(dir, 'skills.lock');
|
|
10
|
+
if (!fs.existsSync(lockPath)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
15
|
+
return JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Write a lockfile deterministically: sorted keys, consistent formatting, trailing newline.
|
|
23
|
+
*/
|
|
24
|
+
export function writeLockfile(lock, directory) {
|
|
25
|
+
const dir = directory ?? process.cwd();
|
|
26
|
+
const lockPath = path.join(dir, 'skills.lock');
|
|
27
|
+
// Sort skill keys alphabetically for determinism
|
|
28
|
+
const sortedSkills = {};
|
|
29
|
+
for (const key of Object.keys(lock.skills).sort()) {
|
|
30
|
+
sortedSkills[key] = lock.skills[key];
|
|
31
|
+
}
|
|
32
|
+
const output = {
|
|
33
|
+
lockfileVersion: lock.lockfileVersion,
|
|
34
|
+
skills: sortedSkills,
|
|
35
|
+
};
|
|
36
|
+
fs.writeFileSync(lockPath, JSON.stringify(output, null, 2) + '\n');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compute the union of all skill permissions from a lockfile.
|
|
40
|
+
* Merges network outbound, filesystem read/write (deduped), and subprocess (OR).
|
|
41
|
+
*/
|
|
42
|
+
export function computeResolvedPermissions(lock) {
|
|
43
|
+
const entries = Object.values(lock.skills);
|
|
44
|
+
if (entries.length === 0) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
const outbound = new Set();
|
|
48
|
+
const fsRead = new Set();
|
|
49
|
+
const fsWrite = new Set();
|
|
50
|
+
let subprocess = false;
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const perms = entry.permissions;
|
|
53
|
+
if (perms.network?.outbound) {
|
|
54
|
+
for (const domain of perms.network.outbound) {
|
|
55
|
+
outbound.add(domain);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (perms.filesystem?.read) {
|
|
59
|
+
for (const pattern of perms.filesystem.read) {
|
|
60
|
+
fsRead.add(pattern);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (perms.filesystem?.write) {
|
|
64
|
+
for (const pattern of perms.filesystem.write) {
|
|
65
|
+
fsWrite.add(pattern);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (perms.subprocess === true) {
|
|
69
|
+
subprocess = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const result = {};
|
|
73
|
+
if (outbound.size > 0) {
|
|
74
|
+
result.network = { outbound: [...outbound].sort() };
|
|
75
|
+
}
|
|
76
|
+
if (fsRead.size > 0 || fsWrite.size > 0) {
|
|
77
|
+
result.filesystem = {};
|
|
78
|
+
if (fsRead.size > 0) {
|
|
79
|
+
result.filesystem.read = [...fsRead].sort();
|
|
80
|
+
}
|
|
81
|
+
if (fsWrite.size > 0) {
|
|
82
|
+
result.filesystem.write = [...fsWrite].sort();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (subprocess) {
|
|
86
|
+
result.subprocess = true;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if resolved permissions fit within the project's permission budget.
|
|
92
|
+
*
|
|
93
|
+
* Returns:
|
|
94
|
+
* - 'pass' if all resolved permissions are within budget
|
|
95
|
+
* - 'fail' if any resolved permission exceeds budget
|
|
96
|
+
* - 'no_budget' if no project permissions are defined
|
|
97
|
+
*/
|
|
98
|
+
export function computeBudgetCheck(resolvedPermissions, projectPermissions) {
|
|
99
|
+
if (projectPermissions === undefined) {
|
|
100
|
+
return 'no_budget';
|
|
101
|
+
}
|
|
102
|
+
// Check subprocess
|
|
103
|
+
if (resolvedPermissions.subprocess === true && projectPermissions.subprocess === false) {
|
|
104
|
+
return 'fail';
|
|
105
|
+
}
|
|
106
|
+
// Check network outbound
|
|
107
|
+
if (resolvedPermissions.network?.outbound) {
|
|
108
|
+
const budgetOutbound = new Set(projectPermissions.network?.outbound ?? []);
|
|
109
|
+
for (const domain of resolvedPermissions.network.outbound) {
|
|
110
|
+
if (!budgetOutbound.has(domain)) {
|
|
111
|
+
return 'fail';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Check filesystem read
|
|
116
|
+
if (resolvedPermissions.filesystem?.read) {
|
|
117
|
+
const budgetRead = new Set(projectPermissions.filesystem?.read ?? []);
|
|
118
|
+
for (const pattern of resolvedPermissions.filesystem.read) {
|
|
119
|
+
if (!budgetRead.has(pattern)) {
|
|
120
|
+
return 'fail';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Check filesystem write
|
|
125
|
+
if (resolvedPermissions.filesystem?.write) {
|
|
126
|
+
const budgetWrite = new Set(projectPermissions.filesystem?.write ?? []);
|
|
127
|
+
for (const pattern of resolvedPermissions.filesystem.write) {
|
|
128
|
+
if (!budgetWrite.has(pattern)) {
|
|
129
|
+
return 'fail';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return 'pass';
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=lockfile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lockfile.js","sourceRoot":"","sources":["../../src/lib/lockfile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export const logger = {
|
|
3
|
+
info: (msg) => console.log(chalk.blue('ℹ'), msg),
|
|
4
|
+
success: (msg) => console.log(chalk.green('✓'), msg),
|
|
5
|
+
warn: (msg) => console.log(chalk.yellow('⚠'), msg),
|
|
6
|
+
error: (msg) => console.error(chalk.red('✗'), msg),
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACxD,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IAC5D,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IAC1D,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;CAC3D,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface PackResult {
|
|
2
|
+
tarball: Buffer;
|
|
3
|
+
integrity: string;
|
|
4
|
+
fileCount: number;
|
|
5
|
+
totalSize: number;
|
|
6
|
+
readme: string;
|
|
7
|
+
files: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Pack a skill directory into a .tgz tarball with integrity hashing.
|
|
11
|
+
*
|
|
12
|
+
* Validates:
|
|
13
|
+
* - skills.json exists and is valid
|
|
14
|
+
* - SKILL.md exists
|
|
15
|
+
* - No symlinks or hardlinks
|
|
16
|
+
* - No path traversal (.. components)
|
|
17
|
+
* - No absolute paths
|
|
18
|
+
* - File count <= 1000
|
|
19
|
+
* - Tarball size <= 50MB
|
|
20
|
+
*/
|
|
21
|
+
export declare function pack(directory: string): Promise<PackResult>;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { create } from 'tar';
|
|
5
|
+
import ignore from 'ignore';
|
|
6
|
+
import { skillsJsonSchema } from '@tank/shared';
|
|
7
|
+
// Limits
|
|
8
|
+
const MAX_PACKAGE_SIZE = 50 * 1024 * 1024; // 50MB
|
|
9
|
+
const MAX_FILE_COUNT = 1000;
|
|
10
|
+
// Default ignore patterns (used when no .tankignore or .gitignore exists)
|
|
11
|
+
const DEFAULT_IGNORES = [
|
|
12
|
+
'node_modules',
|
|
13
|
+
'.git',
|
|
14
|
+
'.env*',
|
|
15
|
+
'*.log',
|
|
16
|
+
'.tank',
|
|
17
|
+
'.DS_Store',
|
|
18
|
+
];
|
|
19
|
+
// Always ignored regardless of ignore file contents
|
|
20
|
+
const ALWAYS_IGNORED = [
|
|
21
|
+
'node_modules',
|
|
22
|
+
'.git',
|
|
23
|
+
];
|
|
24
|
+
// Ignore file names (not packed into tarball)
|
|
25
|
+
const IGNORE_FILES = ['.tankignore', '.gitignore'];
|
|
26
|
+
/**
|
|
27
|
+
* Pack a skill directory into a .tgz tarball with integrity hashing.
|
|
28
|
+
*
|
|
29
|
+
* Validates:
|
|
30
|
+
* - skills.json exists and is valid
|
|
31
|
+
* - SKILL.md exists
|
|
32
|
+
* - No symlinks or hardlinks
|
|
33
|
+
* - No path traversal (.. components)
|
|
34
|
+
* - No absolute paths
|
|
35
|
+
* - File count <= 1000
|
|
36
|
+
* - Tarball size <= 50MB
|
|
37
|
+
*/
|
|
38
|
+
export async function pack(directory) {
|
|
39
|
+
const absDir = path.resolve(directory);
|
|
40
|
+
// 1. Verify directory exists
|
|
41
|
+
if (!fs.existsSync(absDir)) {
|
|
42
|
+
throw new Error(`Directory does not exist: ${absDir}`);
|
|
43
|
+
}
|
|
44
|
+
const stat = fs.statSync(absDir);
|
|
45
|
+
if (!stat.isDirectory()) {
|
|
46
|
+
throw new Error(`Not a directory: ${absDir}`);
|
|
47
|
+
}
|
|
48
|
+
// 2. Verify skills.json exists and is valid
|
|
49
|
+
const skillsJsonPath = path.join(absDir, 'skills.json');
|
|
50
|
+
if (!fs.existsSync(skillsJsonPath)) {
|
|
51
|
+
throw new Error('Missing required file: skills.json');
|
|
52
|
+
}
|
|
53
|
+
let skillsJsonContent;
|
|
54
|
+
try {
|
|
55
|
+
skillsJsonContent = fs.readFileSync(skillsJsonPath, 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new Error('Failed to read skills.json');
|
|
59
|
+
}
|
|
60
|
+
let parsed;
|
|
61
|
+
try {
|
|
62
|
+
parsed = JSON.parse(skillsJsonContent);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new Error('Invalid skills.json: not valid JSON');
|
|
66
|
+
}
|
|
67
|
+
const validation = skillsJsonSchema.safeParse(parsed);
|
|
68
|
+
if (!validation.success) {
|
|
69
|
+
const issues = validation.error.issues
|
|
70
|
+
.map((i) => ` - ${i.path.join('.')}: ${i.message}`)
|
|
71
|
+
.join('\n');
|
|
72
|
+
throw new Error(`Invalid skills.json:\n${issues}`);
|
|
73
|
+
}
|
|
74
|
+
// 3. Verify SKILL.md exists and read its content
|
|
75
|
+
const skillMdPath = path.join(absDir, 'SKILL.md');
|
|
76
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
77
|
+
throw new Error('Missing required file: SKILL.md');
|
|
78
|
+
}
|
|
79
|
+
let readmeContent;
|
|
80
|
+
try {
|
|
81
|
+
readmeContent = fs.readFileSync(skillMdPath, 'utf-8');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw new Error('Failed to read SKILL.md');
|
|
85
|
+
}
|
|
86
|
+
// 4. Build ignore filter
|
|
87
|
+
const ig = buildIgnoreFilter(absDir);
|
|
88
|
+
// 5. Collect files with validation
|
|
89
|
+
const files = collectFiles(absDir, absDir, ig);
|
|
90
|
+
// 6. Enforce file count limit
|
|
91
|
+
if (files.length > MAX_FILE_COUNT) {
|
|
92
|
+
throw new Error(`Too many files: ${files.length} exceeds maximum of ${MAX_FILE_COUNT}`);
|
|
93
|
+
}
|
|
94
|
+
// 7. Calculate total size of source files
|
|
95
|
+
let totalSize = 0;
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const filePath = path.join(absDir, file);
|
|
98
|
+
const fileStat = fs.statSync(filePath);
|
|
99
|
+
totalSize += fileStat.size;
|
|
100
|
+
}
|
|
101
|
+
// 8. Create tarball
|
|
102
|
+
const tarball = await createTarball(absDir, files);
|
|
103
|
+
// 9. Enforce tarball size limit
|
|
104
|
+
if (tarball.length > MAX_PACKAGE_SIZE) {
|
|
105
|
+
throw new Error(`Tarball too large: ${tarball.length} bytes exceeds maximum of ${MAX_PACKAGE_SIZE} bytes (50MB)`);
|
|
106
|
+
}
|
|
107
|
+
// 10. Compute integrity hash
|
|
108
|
+
const hash = crypto.createHash('sha512').update(tarball).digest('base64');
|
|
109
|
+
const integrity = `sha512-${hash}`;
|
|
110
|
+
return {
|
|
111
|
+
tarball,
|
|
112
|
+
integrity,
|
|
113
|
+
fileCount: files.length,
|
|
114
|
+
totalSize,
|
|
115
|
+
readme: readmeContent,
|
|
116
|
+
files,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build an ignore filter from .tankignore, .gitignore, or defaults.
|
|
121
|
+
*/
|
|
122
|
+
function buildIgnoreFilter(dir) {
|
|
123
|
+
const ig = ignore();
|
|
124
|
+
// Always add the forced ignores
|
|
125
|
+
ig.add(ALWAYS_IGNORED);
|
|
126
|
+
// Check for .tankignore first, then .gitignore, then defaults
|
|
127
|
+
const tankIgnorePath = path.join(dir, '.tankignore');
|
|
128
|
+
const gitIgnorePath = path.join(dir, '.gitignore');
|
|
129
|
+
if (fs.existsSync(tankIgnorePath)) {
|
|
130
|
+
const content = fs.readFileSync(tankIgnorePath, 'utf-8');
|
|
131
|
+
ig.add(content);
|
|
132
|
+
// Also ignore the ignore files themselves
|
|
133
|
+
ig.add(IGNORE_FILES);
|
|
134
|
+
}
|
|
135
|
+
else if (fs.existsSync(gitIgnorePath)) {
|
|
136
|
+
const content = fs.readFileSync(gitIgnorePath, 'utf-8');
|
|
137
|
+
ig.add(content);
|
|
138
|
+
ig.add(IGNORE_FILES);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
ig.add(DEFAULT_IGNORES);
|
|
142
|
+
}
|
|
143
|
+
return ig;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Recursively collect files from a directory, applying ignore rules and security checks.
|
|
147
|
+
*/
|
|
148
|
+
function collectFiles(baseDir, currentDir, ig) {
|
|
149
|
+
const files = [];
|
|
150
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
153
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
154
|
+
// Security: check for path traversal
|
|
155
|
+
if (relativePath.split(path.sep).includes('..')) {
|
|
156
|
+
throw new Error(`Path traversal detected: "${relativePath}" contains ".." component`);
|
|
157
|
+
}
|
|
158
|
+
// Security: check for absolute paths
|
|
159
|
+
if (path.isAbsolute(relativePath)) {
|
|
160
|
+
throw new Error(`Absolute path detected: "${relativePath}"`);
|
|
161
|
+
}
|
|
162
|
+
// Security: check for symlinks using lstat (not stat which follows symlinks)
|
|
163
|
+
const lstatResult = fs.lstatSync(fullPath);
|
|
164
|
+
if (lstatResult.isSymbolicLink()) {
|
|
165
|
+
throw new Error(`Symlink detected: "${relativePath}" — symlinks are not allowed in skill packages`);
|
|
166
|
+
}
|
|
167
|
+
// Check if this path should be ignored
|
|
168
|
+
// For directories, append '/' so ignore patterns like 'dir/' work correctly
|
|
169
|
+
const pathForIgnore = lstatResult.isDirectory()
|
|
170
|
+
? relativePath + '/'
|
|
171
|
+
: relativePath;
|
|
172
|
+
if (ig.ignores(pathForIgnore)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (lstatResult.isDirectory()) {
|
|
176
|
+
// Recurse into subdirectory
|
|
177
|
+
const subFiles = collectFiles(baseDir, fullPath, ig);
|
|
178
|
+
files.push(...subFiles);
|
|
179
|
+
}
|
|
180
|
+
else if (lstatResult.isFile()) {
|
|
181
|
+
files.push(relativePath);
|
|
182
|
+
}
|
|
183
|
+
// Skip other types (block devices, character devices, FIFOs, sockets)
|
|
184
|
+
}
|
|
185
|
+
return files;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a gzipped tarball from the given files in the directory.
|
|
189
|
+
*/
|
|
190
|
+
async function createTarball(cwd, files) {
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
const chunks = [];
|
|
193
|
+
// tar.create without `file` returns a readable stream
|
|
194
|
+
const stream = create({
|
|
195
|
+
gzip: true,
|
|
196
|
+
cwd,
|
|
197
|
+
portable: true, // Omit system-specific metadata
|
|
198
|
+
}, files);
|
|
199
|
+
stream.on('data', (chunk) => {
|
|
200
|
+
chunks.push(chunk);
|
|
201
|
+
});
|
|
202
|
+
stream.on('end', () => {
|
|
203
|
+
resolve(Buffer.concat(chunks));
|
|
204
|
+
});
|
|
205
|
+
stream.on('error', (err) => {
|
|
206
|
+
reject(err);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=packer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packer.js","sourceRoot":"","sources":["../../src/lib/packer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkForUpgrade(configDir?: string): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { getConfigDir } from './config.js';
|
|
5
|
+
import { VERSION } from '../version.js';
|
|
6
|
+
export async function checkForUpgrade(configDir) {
|
|
7
|
+
try {
|
|
8
|
+
if (process.env.TANK_NO_UPDATE_CHECK || process.env.CI) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const cacheDir = getConfigDir(configDir);
|
|
12
|
+
const cachePath = path.join(cacheDir, 'upgrade_check.json');
|
|
13
|
+
let cache = null;
|
|
14
|
+
try {
|
|
15
|
+
const raw = fs.readFileSync(cachePath, 'utf-8');
|
|
16
|
+
cache = JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// File doesn't exist or invalid JSON — treat as stale
|
|
20
|
+
}
|
|
21
|
+
const isFresh = cache !== null && (Date.now() - cache.lastCheck) < 24 * 60 * 60 * 1000;
|
|
22
|
+
if (isFresh && cache !== null) {
|
|
23
|
+
if (cache.latestVersion !== VERSION) {
|
|
24
|
+
console.error(`\n ${chalk.cyan('ℹ')} New version available: ${chalk.gray(VERSION)} → ${chalk.green(cache.latestVersion)}`);
|
|
25
|
+
console.error(` Run ${chalk.cyan('`tank upgrade`')} to update.\n`);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const res = await fetch('https://api.github.com/repos/tankpkg/tank/releases/latest', {
|
|
30
|
+
headers: { 'User-Agent': `tank-cli/${VERSION}` },
|
|
31
|
+
signal: AbortSignal.timeout(3000),
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const data = (await res.json());
|
|
37
|
+
const latestVersion = data.tag_name.replace(/^v/, '');
|
|
38
|
+
if (!fs.existsSync(cacheDir)) {
|
|
39
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
const newCache = { lastCheck: Date.now(), latestVersion };
|
|
42
|
+
fs.writeFileSync(cachePath, JSON.stringify(newCache, null, 2) + '\n');
|
|
43
|
+
if (latestVersion !== VERSION) {
|
|
44
|
+
console.error(`\n ${chalk.cyan('ℹ')} New version available: ${chalk.gray(VERSION)} → ${chalk.green(latestVersion)}`);
|
|
45
|
+
console.error(` Run ${chalk.cyan('`tank upgrade`')} to update.\n`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Silently swallow ALL errors — background check must never cause CLI to fail
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=upgrade-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade-check.js","sourceRoot":"","sources":["../../src/lib/upgrade-check.ts"],"names":[],"mappings":"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"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAExD,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;AAC9C,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,OAAO,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tankpkg/cli",
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Security-first package manager for AI agent skills",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tank": "dist/bin/tank.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^22.19.11",
|
|
17
|
+
"@types/tar": "^6.1.13",
|
|
18
|
+
"esbuild": "^0.25.9",
|
|
19
|
+
"vitest": "^3"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@inquirer/prompts": "^8.2.0",
|
|
23
|
+
"chalk": "^5.6.2",
|
|
24
|
+
"commander": "^14.0.3",
|
|
25
|
+
"ignore": "^7.0.5",
|
|
26
|
+
"open": "^11.0.0",
|
|
27
|
+
"ora": "^9.3.0",
|
|
28
|
+
"pino": "^10.3.1",
|
|
29
|
+
"pino-loki": "^3.0.0",
|
|
30
|
+
"tar": "^7.5.8",
|
|
31
|
+
"@tank/shared": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
|
|
35
|
+
"build:bundle": "esbuild dist/bin/tank.js --bundle --platform=node --format=esm --outfile=build/tank-bundle.mjs --external:fsevents",
|
|
36
|
+
"build:sea": "bash scripts/build-binary.sh",
|
|
37
|
+
"build:binary": "pnpm run build && pnpm run build:bundle && pnpm run build:sea",
|
|
38
|
+
"test": "vitest run"
|
|
39
|
+
}
|
|
40
|
+
}
|