@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,550 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { extract } from 'tar';
|
|
7
|
+
import { resolve, LOCKFILE_VERSION } from '@tank/shared';
|
|
8
|
+
import { getConfig } from '../lib/config.js';
|
|
9
|
+
import { logger } from '../lib/logger.js';
|
|
10
|
+
import { prepareAgentSkillDir } from '../lib/frontmatter.js';
|
|
11
|
+
import { linkSkillToAgents } from '../lib/linker.js';
|
|
12
|
+
import { detectInstalledAgents, getGlobalSkillsDir, getGlobalAgentSkillsDir } from '../lib/agents.js';
|
|
13
|
+
import { USER_AGENT } from '../version.js';
|
|
14
|
+
const MAX_UNCOMPRESSED_SIZE = 100 * 1024 * 1024; // 100MB
|
|
15
|
+
/**
|
|
16
|
+
* Install a skill from the Tank registry.
|
|
17
|
+
*
|
|
18
|
+
* Flow:
|
|
19
|
+
* 1. Read skills.json from directory (must exist)
|
|
20
|
+
* 2. Fetch available versions
|
|
21
|
+
* 3. Resolve best version using semver
|
|
22
|
+
* 4. Check if already installed (skip if same version in lockfile)
|
|
23
|
+
* 5. Fetch version metadata + download URL
|
|
24
|
+
* 6. Check permission budget
|
|
25
|
+
* 7. Download tarball
|
|
26
|
+
* 8. Verify integrity (sha512)
|
|
27
|
+
* 9. Extract tarball safely
|
|
28
|
+
* 10. Update skills.json
|
|
29
|
+
* 11. Update skills.lock
|
|
30
|
+
*/
|
|
31
|
+
export async function installCommand(options) {
|
|
32
|
+
const { name, versionRange = '*', directory = process.cwd(), configDir, global = false, homedir, } = options;
|
|
33
|
+
const config = getConfig(configDir);
|
|
34
|
+
const resolvedHome = homedir ?? os.homedir();
|
|
35
|
+
const requestHeaders = { 'User-Agent': USER_AGENT };
|
|
36
|
+
if (config.token) {
|
|
37
|
+
requestHeaders.Authorization = `Bearer ${config.token}`;
|
|
38
|
+
}
|
|
39
|
+
// 1. Read or create skills.json
|
|
40
|
+
const skillsJsonPath = path.join(directory, 'skills.json');
|
|
41
|
+
let skillsJson = { skills: {} };
|
|
42
|
+
if (!global) {
|
|
43
|
+
if (!fs.existsSync(skillsJsonPath)) {
|
|
44
|
+
skillsJson = { skills: {} };
|
|
45
|
+
fs.writeFileSync(skillsJsonPath, JSON.stringify(skillsJson, null, 2) + '\n');
|
|
46
|
+
logger.info('Created skills.json');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
try {
|
|
50
|
+
const raw = fs.readFileSync(skillsJsonPath, 'utf-8');
|
|
51
|
+
skillsJson = JSON.parse(raw);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error('Failed to read or parse skills.json');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Read existing lockfile if present
|
|
59
|
+
const lockPath = global
|
|
60
|
+
? path.join(resolvedHome, '.tank', 'skills.lock')
|
|
61
|
+
: path.join(directory, 'skills.lock');
|
|
62
|
+
let lock = { lockfileVersion: LOCKFILE_VERSION, skills: {} };
|
|
63
|
+
if (fs.existsSync(lockPath)) {
|
|
64
|
+
try {
|
|
65
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
66
|
+
lock = JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// If lockfile is corrupt, start fresh
|
|
70
|
+
lock = { lockfileVersion: LOCKFILE_VERSION, skills: {} };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const spinner = ora('Resolving versions...').start();
|
|
74
|
+
// 2. Fetch available versions
|
|
75
|
+
const encodedName = encodeURIComponent(name);
|
|
76
|
+
const versionsUrl = `${config.registry}/api/v1/skills/${encodedName}/versions`;
|
|
77
|
+
let versionsRes;
|
|
78
|
+
try {
|
|
79
|
+
versionsRes = await fetch(versionsUrl, {
|
|
80
|
+
headers: requestHeaders,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
spinner.fail('Failed to fetch versions');
|
|
85
|
+
throw new Error(`Network error fetching versions: ${err instanceof Error ? err.message : String(err)}`);
|
|
86
|
+
}
|
|
87
|
+
if (!versionsRes.ok) {
|
|
88
|
+
spinner.fail('Failed to fetch versions');
|
|
89
|
+
if (versionsRes.status === 403) {
|
|
90
|
+
throw new Error('Token lacks required scope: skills:read');
|
|
91
|
+
}
|
|
92
|
+
if (versionsRes.status === 404) {
|
|
93
|
+
throw new Error(`Skill not found or no access: ${name}`);
|
|
94
|
+
}
|
|
95
|
+
const body = await versionsRes.json().catch(() => ({}));
|
|
96
|
+
throw new Error(body.error ?? versionsRes.statusText);
|
|
97
|
+
}
|
|
98
|
+
const versionsData = await versionsRes.json();
|
|
99
|
+
const availableVersions = versionsData.versions.map((v) => v.version);
|
|
100
|
+
// 3. Resolve best version
|
|
101
|
+
const resolved = resolve(versionRange, availableVersions);
|
|
102
|
+
if (!resolved) {
|
|
103
|
+
spinner.fail('Version resolution failed');
|
|
104
|
+
throw new Error(`No version of ${name} satisfies range "${versionRange}". Available: ${availableVersions.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
// 4. Check if already installed
|
|
107
|
+
const lockKey = `${name}@${resolved}`;
|
|
108
|
+
if (lock.skills[lockKey]) {
|
|
109
|
+
spinner.stop();
|
|
110
|
+
logger.info(`${name}@${resolved} is already installed`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// 5. Fetch version metadata
|
|
114
|
+
spinner.text = `Fetching ${name}@${resolved}...`;
|
|
115
|
+
const metaUrl = `${config.registry}/api/v1/skills/${encodedName}/${resolved}`;
|
|
116
|
+
let metaRes;
|
|
117
|
+
try {
|
|
118
|
+
metaRes = await fetch(metaUrl, {
|
|
119
|
+
headers: requestHeaders,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
spinner.fail('Failed to fetch version metadata');
|
|
124
|
+
throw new Error(`Network error fetching metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
125
|
+
}
|
|
126
|
+
if (!metaRes.ok) {
|
|
127
|
+
spinner.fail('Failed to fetch version metadata');
|
|
128
|
+
if (metaRes.status === 403) {
|
|
129
|
+
throw new Error('Token lacks required scope: skills:read');
|
|
130
|
+
}
|
|
131
|
+
if (metaRes.status === 404) {
|
|
132
|
+
throw new Error(`Skill not found or no access: ${name}@${resolved}`);
|
|
133
|
+
}
|
|
134
|
+
const body = await metaRes.json().catch(() => ({}));
|
|
135
|
+
throw new Error(body.error ?? metaRes.statusText);
|
|
136
|
+
}
|
|
137
|
+
const metadata = await metaRes.json();
|
|
138
|
+
// 6. Check permission budget
|
|
139
|
+
const projectPermissions = global ? undefined : skillsJson.permissions;
|
|
140
|
+
const skillPermissions = metadata.permissions;
|
|
141
|
+
if (!global) {
|
|
142
|
+
if (!projectPermissions) {
|
|
143
|
+
logger.warn('No permission budget defined in skills.json. Install proceeding without permission checks.');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
checkPermissionBudget(projectPermissions, skillPermissions, name);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// 6.5. Check audit score threshold
|
|
150
|
+
const auditMinScore = global ? undefined : skillsJson.audit?.min_score;
|
|
151
|
+
if (!global && auditMinScore !== undefined) {
|
|
152
|
+
if (metadata.auditScore === null || metadata.auditScore === undefined) {
|
|
153
|
+
logger.warn(`Audit score not yet available for ${name}. Install proceeding without audit score check.`);
|
|
154
|
+
}
|
|
155
|
+
else if (metadata.auditScore < auditMinScore) {
|
|
156
|
+
throw new Error(`Audit score ${metadata.auditScore} for ${name} is below minimum threshold ${auditMinScore} defined in skills.json`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// 7. Download tarball
|
|
160
|
+
spinner.text = `Downloading ${name}@${resolved}...`;
|
|
161
|
+
let downloadRes;
|
|
162
|
+
try {
|
|
163
|
+
downloadRes = await fetch(metadata.downloadUrl);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
spinner.fail('Download failed');
|
|
167
|
+
throw new Error(`Network error downloading tarball: ${err instanceof Error ? err.message : String(err)}`);
|
|
168
|
+
}
|
|
169
|
+
if (!downloadRes.ok) {
|
|
170
|
+
spinner.fail('Download failed');
|
|
171
|
+
throw new Error(`Failed to download tarball: ${downloadRes.status} ${downloadRes.statusText}`);
|
|
172
|
+
}
|
|
173
|
+
const tarballBuffer = Buffer.from(await downloadRes.arrayBuffer());
|
|
174
|
+
// 8. Verify integrity
|
|
175
|
+
spinner.text = 'Verifying integrity...';
|
|
176
|
+
const hash = crypto.createHash('sha512').update(tarballBuffer).digest('base64');
|
|
177
|
+
const computedIntegrity = `sha512-${hash}`;
|
|
178
|
+
if (computedIntegrity !== metadata.integrity) {
|
|
179
|
+
spinner.fail('Integrity check failed');
|
|
180
|
+
throw new Error(`Integrity mismatch for ${name}@${resolved}. Expected: ${metadata.integrity}, Got: ${computedIntegrity}`);
|
|
181
|
+
}
|
|
182
|
+
// 9. Extract tarball safely
|
|
183
|
+
spinner.text = `Extracting ${name}@${resolved}...`;
|
|
184
|
+
const extractDir = global
|
|
185
|
+
? getGlobalExtractDir(resolvedHome, name)
|
|
186
|
+
: getExtractDir(directory, name);
|
|
187
|
+
// Create extraction directory
|
|
188
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
189
|
+
// Extract with safety checks
|
|
190
|
+
await extractSafely(tarballBuffer, extractDir);
|
|
191
|
+
// 10. Update skills.json
|
|
192
|
+
if (!global) {
|
|
193
|
+
const skills = (skillsJson.skills ?? {});
|
|
194
|
+
// Use the provided range if explicit, otherwise use ^{resolved}
|
|
195
|
+
skills[name] = versionRange === '*' ? `^${resolved}` : versionRange;
|
|
196
|
+
skillsJson.skills = skills;
|
|
197
|
+
fs.writeFileSync(skillsJsonPath, JSON.stringify(skillsJson, null, 2) + '\n');
|
|
198
|
+
}
|
|
199
|
+
// 11. Update skills.lock
|
|
200
|
+
lock.skills[lockKey] = {
|
|
201
|
+
resolved: metadata.downloadUrl,
|
|
202
|
+
integrity: computedIntegrity,
|
|
203
|
+
permissions: skillPermissions ?? {},
|
|
204
|
+
audit_score: metadata.auditScore ?? null,
|
|
205
|
+
};
|
|
206
|
+
// Sort keys alphabetically
|
|
207
|
+
const sortedSkills = {};
|
|
208
|
+
for (const key of Object.keys(lock.skills).sort()) {
|
|
209
|
+
sortedSkills[key] = lock.skills[key];
|
|
210
|
+
}
|
|
211
|
+
lock.skills = sortedSkills;
|
|
212
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
213
|
+
fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
214
|
+
// 12. Agent linking (always-on, failures are warnings)
|
|
215
|
+
try {
|
|
216
|
+
const agentSkillsBaseDir = global
|
|
217
|
+
? getGlobalAgentSkillsDir(resolvedHome)
|
|
218
|
+
: path.join(directory, '.tank', 'agent-skills');
|
|
219
|
+
const agentSkillDir = prepareAgentSkillDir({
|
|
220
|
+
skillName: name,
|
|
221
|
+
extractDir,
|
|
222
|
+
agentSkillsBaseDir,
|
|
223
|
+
description: metadata.description,
|
|
224
|
+
});
|
|
225
|
+
const linkResult = linkSkillToAgents({
|
|
226
|
+
skillName: name,
|
|
227
|
+
sourceDir: agentSkillDir,
|
|
228
|
+
linksDir: global ? path.join(resolvedHome, '.tank') : path.join(directory, '.tank'),
|
|
229
|
+
source: global ? 'global' : 'local',
|
|
230
|
+
homedir: options.homedir,
|
|
231
|
+
});
|
|
232
|
+
const detectedAgents = detectInstalledAgents(options.homedir);
|
|
233
|
+
if (detectedAgents.length === 0) {
|
|
234
|
+
logger.warn('No agents detected for linking');
|
|
235
|
+
}
|
|
236
|
+
if (linkResult.linked.length > 0) {
|
|
237
|
+
logger.info(`Linked to ${linkResult.linked.length} agent(s)`);
|
|
238
|
+
}
|
|
239
|
+
if (linkResult.failed.length > 0) {
|
|
240
|
+
for (const f of linkResult.failed) {
|
|
241
|
+
logger.warn(`Failed to link to ${f.agentId}: ${f.error}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
logger.warn('Agent linking skipped (non-fatal)');
|
|
247
|
+
}
|
|
248
|
+
spinner.succeed(`Installed ${name}@${resolved}`);
|
|
249
|
+
}
|
|
250
|
+
export async function installFromLockfile(options) {
|
|
251
|
+
const { directory = process.cwd(), configDir, global = false, homedir } = options;
|
|
252
|
+
const resolvedHome = homedir ?? os.homedir();
|
|
253
|
+
const config = getConfig(configDir);
|
|
254
|
+
const requestHeaders = { 'User-Agent': USER_AGENT };
|
|
255
|
+
if (config.token) {
|
|
256
|
+
requestHeaders.Authorization = `Bearer ${config.token}`;
|
|
257
|
+
}
|
|
258
|
+
const lockPath = global
|
|
259
|
+
? path.join(resolvedHome, '.tank', 'skills.lock')
|
|
260
|
+
: path.join(directory, 'skills.lock');
|
|
261
|
+
if (!fs.existsSync(lockPath)) {
|
|
262
|
+
throw new Error(`No skills.lock found in ${directory}`);
|
|
263
|
+
}
|
|
264
|
+
let lock;
|
|
265
|
+
try {
|
|
266
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
267
|
+
lock = JSON.parse(raw);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
throw new Error('Failed to read or parse skills.lock');
|
|
271
|
+
}
|
|
272
|
+
const entries = Object.entries(lock.skills);
|
|
273
|
+
if (entries.length === 0) {
|
|
274
|
+
logger.info('No skills in lockfile');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const spinner = ora('Installing from lockfile...').start();
|
|
278
|
+
const skillsDir = global
|
|
279
|
+
? getGlobalSkillsDir(resolvedHome)
|
|
280
|
+
: path.join(directory, '.tank', 'skills');
|
|
281
|
+
try {
|
|
282
|
+
for (const [key, entry] of entries) {
|
|
283
|
+
const skillName = parseLockKey(key);
|
|
284
|
+
const version = parseVersionFromLockKey(key);
|
|
285
|
+
spinner.text = `Installing ${key}...`;
|
|
286
|
+
// Fetch metadata from API to record download and get fresh signed URL
|
|
287
|
+
const encodedName = encodeURIComponent(skillName);
|
|
288
|
+
const metaUrl = `${config.registry}/api/v1/skills/${encodedName}/${version}`;
|
|
289
|
+
let metaRes;
|
|
290
|
+
try {
|
|
291
|
+
metaRes = await fetch(metaUrl, {
|
|
292
|
+
headers: requestHeaders,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
throw new Error(`Network error fetching ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
297
|
+
}
|
|
298
|
+
if (!metaRes.ok) {
|
|
299
|
+
if (metaRes.status === 404) {
|
|
300
|
+
throw new Error(`Skill or version not found: ${key}`);
|
|
301
|
+
}
|
|
302
|
+
const body = await metaRes.json().catch(() => ({}));
|
|
303
|
+
throw new Error(`Failed to fetch ${key}: ${body.error ?? metaRes.statusText}`);
|
|
304
|
+
}
|
|
305
|
+
const metadata = await metaRes.json();
|
|
306
|
+
const downloadUrl = metadata.downloadUrl;
|
|
307
|
+
const downloadRes = await fetch(downloadUrl);
|
|
308
|
+
if (!downloadRes.ok) {
|
|
309
|
+
throw new Error(`Failed to download ${key}: ${downloadRes.status} ${downloadRes.statusText}`);
|
|
310
|
+
}
|
|
311
|
+
const tarballBuffer = Buffer.from(await downloadRes.arrayBuffer());
|
|
312
|
+
const hash = crypto.createHash('sha512').update(tarballBuffer).digest('base64');
|
|
313
|
+
const computedIntegrity = `sha512-${hash}`;
|
|
314
|
+
if (computedIntegrity !== entry.integrity) {
|
|
315
|
+
throw new Error(`Integrity mismatch for ${key}. Expected: ${entry.integrity}, Got: ${computedIntegrity}`);
|
|
316
|
+
}
|
|
317
|
+
const extractDir = global
|
|
318
|
+
? getGlobalExtractDir(resolvedHome, skillName)
|
|
319
|
+
: getExtractDir(directory, skillName);
|
|
320
|
+
if (fs.existsSync(extractDir)) {
|
|
321
|
+
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
322
|
+
}
|
|
323
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
324
|
+
await extractSafely(tarballBuffer, extractDir);
|
|
325
|
+
if (global) {
|
|
326
|
+
try {
|
|
327
|
+
const agentSkillsBaseDir = getGlobalAgentSkillsDir(resolvedHome);
|
|
328
|
+
const agentSkillDir = prepareAgentSkillDir({
|
|
329
|
+
skillName,
|
|
330
|
+
extractDir,
|
|
331
|
+
agentSkillsBaseDir,
|
|
332
|
+
});
|
|
333
|
+
const linkResult = linkSkillToAgents({
|
|
334
|
+
skillName,
|
|
335
|
+
sourceDir: agentSkillDir,
|
|
336
|
+
linksDir: path.join(resolvedHome, '.tank'),
|
|
337
|
+
source: 'global',
|
|
338
|
+
homedir,
|
|
339
|
+
});
|
|
340
|
+
const detectedAgents = detectInstalledAgents(homedir);
|
|
341
|
+
if (detectedAgents.length === 0) {
|
|
342
|
+
logger.warn('No agents detected for linking');
|
|
343
|
+
}
|
|
344
|
+
if (linkResult.linked.length > 0) {
|
|
345
|
+
logger.info(`Linked to ${linkResult.linked.length} agent(s)`);
|
|
346
|
+
}
|
|
347
|
+
if (linkResult.failed.length > 0) {
|
|
348
|
+
for (const f of linkResult.failed) {
|
|
349
|
+
logger.warn(`Failed to link to ${f.agentId}: ${f.error}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
logger.warn('Agent linking skipped (non-fatal)');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
spinner.succeed(`Installed ${entries.length} skill${entries.length === 1 ? '' : 's'} from lockfile`);
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
spinner.fail('Install from lockfile failed');
|
|
362
|
+
if (fs.existsSync(skillsDir)) {
|
|
363
|
+
fs.rmSync(skillsDir, { recursive: true, force: true });
|
|
364
|
+
}
|
|
365
|
+
throw err;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
export async function installAll(options) {
|
|
369
|
+
const { directory = process.cwd(), configDir, global = false, homedir } = options;
|
|
370
|
+
const resolvedHome = homedir ?? os.homedir();
|
|
371
|
+
const lockPath = global
|
|
372
|
+
? path.join(resolvedHome, '.tank', 'skills.lock')
|
|
373
|
+
: path.join(directory, 'skills.lock');
|
|
374
|
+
const skillsJsonPath = path.join(directory, 'skills.json');
|
|
375
|
+
if (fs.existsSync(lockPath)) {
|
|
376
|
+
return installFromLockfile({ directory, configDir, global, homedir });
|
|
377
|
+
}
|
|
378
|
+
if (global) {
|
|
379
|
+
logger.info('No skills.lock found — nothing to install');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (!fs.existsSync(skillsJsonPath)) {
|
|
383
|
+
logger.info('No skills.json found — nothing to install');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
let skillsJson;
|
|
387
|
+
try {
|
|
388
|
+
const raw = fs.readFileSync(skillsJsonPath, 'utf-8');
|
|
389
|
+
skillsJson = JSON.parse(raw);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
throw new Error('Failed to read or parse skills.json');
|
|
393
|
+
}
|
|
394
|
+
const skills = (skillsJson.skills ?? {});
|
|
395
|
+
const skillEntries = Object.entries(skills);
|
|
396
|
+
if (skillEntries.length === 0) {
|
|
397
|
+
logger.info('No skills defined in skills.json');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
for (const [name, versionRange] of skillEntries) {
|
|
401
|
+
await installCommand({ name, versionRange, directory, configDir, global, homedir });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function parseLockKey(key) {
|
|
405
|
+
const lastAt = key.lastIndexOf('@');
|
|
406
|
+
if (lastAt <= 0) {
|
|
407
|
+
throw new Error(`Invalid lockfile key: ${key}`);
|
|
408
|
+
}
|
|
409
|
+
return key.slice(0, lastAt);
|
|
410
|
+
}
|
|
411
|
+
function parseVersionFromLockKey(key) {
|
|
412
|
+
const lastAt = key.lastIndexOf('@');
|
|
413
|
+
if (lastAt <= 0 || lastAt === key.length - 1) {
|
|
414
|
+
throw new Error(`Invalid lockfile key: ${key}`);
|
|
415
|
+
}
|
|
416
|
+
return key.slice(lastAt + 1);
|
|
417
|
+
}
|
|
418
|
+
function getExtractDir(projectDir, skillName) {
|
|
419
|
+
if (skillName.startsWith('@')) {
|
|
420
|
+
const [scope, name] = skillName.split('/');
|
|
421
|
+
return path.join(projectDir, '.tank', 'skills', scope, name);
|
|
422
|
+
}
|
|
423
|
+
return path.join(projectDir, '.tank', 'skills', skillName);
|
|
424
|
+
}
|
|
425
|
+
function getGlobalExtractDir(homedir, skillName) {
|
|
426
|
+
const globalDir = path.join(homedir, '.tank', 'skills');
|
|
427
|
+
if (skillName.startsWith('@')) {
|
|
428
|
+
const [scope, name] = skillName.split('/');
|
|
429
|
+
return path.join(globalDir, scope, name);
|
|
430
|
+
}
|
|
431
|
+
return path.join(globalDir, skillName);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Extract a tarball safely with security checks.
|
|
435
|
+
* Rejects: absolute paths, path traversal (..), symlinks/hardlinks.
|
|
436
|
+
* Enforces max uncompressed size.
|
|
437
|
+
*/
|
|
438
|
+
async function extractSafely(tarball, destDir) {
|
|
439
|
+
// Write tarball to a temp file for extraction
|
|
440
|
+
const tmpTarball = path.join(destDir, '.tmp-tarball.tgz');
|
|
441
|
+
fs.writeFileSync(tmpTarball, tarball);
|
|
442
|
+
try {
|
|
443
|
+
await extract({
|
|
444
|
+
file: tmpTarball,
|
|
445
|
+
cwd: destDir,
|
|
446
|
+
// Safety: reject entries that try to escape the extraction directory
|
|
447
|
+
filter: (entryPath) => {
|
|
448
|
+
// Reject absolute paths
|
|
449
|
+
if (path.isAbsolute(entryPath)) {
|
|
450
|
+
throw new Error(`Absolute path in tarball: ${entryPath}`);
|
|
451
|
+
}
|
|
452
|
+
// Reject path traversal
|
|
453
|
+
if (entryPath.split('/').includes('..') || entryPath.split(path.sep).includes('..')) {
|
|
454
|
+
throw new Error(`Path traversal in tarball: ${entryPath}`);
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
},
|
|
458
|
+
onReadEntry: (entry) => {
|
|
459
|
+
// Reject symlinks and hardlinks
|
|
460
|
+
if (entry.type === 'SymbolicLink' || entry.type === 'Link') {
|
|
461
|
+
throw new Error(`Symlink/hardlink in tarball: ${entry.path}`);
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
finally {
|
|
467
|
+
// Clean up temp tarball
|
|
468
|
+
if (fs.existsSync(tmpTarball)) {
|
|
469
|
+
fs.unlinkSync(tmpTarball);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Check if a skill's permissions fit within the project's permission budget.
|
|
475
|
+
* Throws if any permission exceeds the budget.
|
|
476
|
+
*/
|
|
477
|
+
function checkPermissionBudget(budget, skillPerms, skillName) {
|
|
478
|
+
if (!skillPerms)
|
|
479
|
+
return;
|
|
480
|
+
// Check subprocess
|
|
481
|
+
if (skillPerms.subprocess === true && budget.subprocess !== true) {
|
|
482
|
+
throw new Error(`Permission denied: ${skillName} requires subprocess access, but project budget does not allow it`);
|
|
483
|
+
}
|
|
484
|
+
// Check network outbound
|
|
485
|
+
if (skillPerms.network?.outbound && skillPerms.network.outbound.length > 0) {
|
|
486
|
+
const budgetDomains = budget.network?.outbound ?? [];
|
|
487
|
+
for (const domain of skillPerms.network.outbound) {
|
|
488
|
+
if (!isDomainAllowed(domain, budgetDomains)) {
|
|
489
|
+
throw new Error(`Permission denied: ${skillName} requests network access to "${domain}", which is not in the project's permission budget`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// Check filesystem read
|
|
494
|
+
if (skillPerms.filesystem?.read && skillPerms.filesystem.read.length > 0) {
|
|
495
|
+
const budgetPaths = budget.filesystem?.read ?? [];
|
|
496
|
+
for (const p of skillPerms.filesystem.read) {
|
|
497
|
+
if (!isPathAllowed(p, budgetPaths)) {
|
|
498
|
+
throw new Error(`Permission denied: ${skillName} requests filesystem read access to "${p}", which is not in the project's permission budget`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Check filesystem write
|
|
503
|
+
if (skillPerms.filesystem?.write && skillPerms.filesystem.write.length > 0) {
|
|
504
|
+
const budgetPaths = budget.filesystem?.write ?? [];
|
|
505
|
+
for (const p of skillPerms.filesystem.write) {
|
|
506
|
+
if (!isPathAllowed(p, budgetPaths)) {
|
|
507
|
+
throw new Error(`Permission denied: ${skillName} requests filesystem write access to "${p}", which is not in the project's permission budget`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Check if a domain is allowed by the budget's domain list.
|
|
514
|
+
* Supports wildcard matching: *.example.com matches sub.example.com
|
|
515
|
+
*/
|
|
516
|
+
function isDomainAllowed(domain, allowedDomains) {
|
|
517
|
+
for (const allowed of allowedDomains) {
|
|
518
|
+
if (allowed === domain)
|
|
519
|
+
return true;
|
|
520
|
+
// Wildcard matching: *.example.com
|
|
521
|
+
if (allowed.startsWith('*.')) {
|
|
522
|
+
const suffix = allowed.slice(1); // .example.com
|
|
523
|
+
if (domain.endsWith(suffix) || domain === allowed.slice(2)) {
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
// Also match if the skill requests the same wildcard pattern
|
|
527
|
+
if (domain === allowed)
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Check if a path is allowed by the budget's path list.
|
|
535
|
+
* Simple subset check: skill path must match one of the budget paths.
|
|
536
|
+
*/
|
|
537
|
+
function isPathAllowed(requestedPath, allowedPaths) {
|
|
538
|
+
for (const allowed of allowedPaths) {
|
|
539
|
+
if (allowed === requestedPath)
|
|
540
|
+
return true;
|
|
541
|
+
// If budget allows ./src/** and skill requests ./src/foo, it's allowed
|
|
542
|
+
if (allowed.endsWith('/**')) {
|
|
543
|
+
const prefix = allowed.slice(0, -3); // ./src
|
|
544
|
+
if (requestedPath.startsWith(prefix))
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { detectInstalledAgents, getGlobalAgentSkillsDir } from '../lib/agents.js';
|
|
5
|
+
import { linkSkillToAgents } from '../lib/linker.js';
|
|
6
|
+
import { prepareAgentSkillDir, hasFrontmatter } from '../lib/frontmatter.js';
|
|
7
|
+
import { readGlobalLinks } from '../lib/links.js';
|
|
8
|
+
import { logger } from '../lib/logger.js';
|
|
9
|
+
export async function linkCommand(options = {}) {
|
|
10
|
+
const workDir = options.directory ?? process.cwd();
|
|
11
|
+
const homedir = options.homedir ?? os.homedir();
|
|
12
|
+
const skillsJsonPath = path.join(workDir, 'skills.json');
|
|
13
|
+
if (!fs.existsSync(skillsJsonPath)) {
|
|
14
|
+
throw new Error('No skills.json found. Run this command from a skill directory.');
|
|
15
|
+
}
|
|
16
|
+
let skillsJson;
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(skillsJsonPath, 'utf-8');
|
|
19
|
+
skillsJson = JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new Error('Failed to read or parse skills.json');
|
|
23
|
+
}
|
|
24
|
+
const skillName = skillsJson.name;
|
|
25
|
+
if (typeof skillName !== 'string' || skillName.trim().length === 0) {
|
|
26
|
+
throw new Error("Missing 'name' in skills.json");
|
|
27
|
+
}
|
|
28
|
+
const description = typeof skillsJson.description === 'string'
|
|
29
|
+
? skillsJson.description
|
|
30
|
+
: undefined;
|
|
31
|
+
const agents = detectInstalledAgents(options.homedir);
|
|
32
|
+
if (agents.length === 0) {
|
|
33
|
+
logger.info('No AI agents detected. Skills linked to agents will be available once agents are installed.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const skillMdPath = path.join(workDir, 'SKILL.md');
|
|
37
|
+
let sourceDir = workDir;
|
|
38
|
+
if (fs.existsSync(skillMdPath)) {
|
|
39
|
+
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
|
40
|
+
if (!hasFrontmatter(content)) {
|
|
41
|
+
sourceDir = prepareAgentSkillDir({
|
|
42
|
+
skillName,
|
|
43
|
+
extractDir: workDir,
|
|
44
|
+
agentSkillsBaseDir: getGlobalAgentSkillsDir(homedir),
|
|
45
|
+
description,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
sourceDir = prepareAgentSkillDir({
|
|
51
|
+
skillName,
|
|
52
|
+
extractDir: workDir,
|
|
53
|
+
agentSkillsBaseDir: getGlobalAgentSkillsDir(homedir),
|
|
54
|
+
description,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
void readGlobalLinks(homedir);
|
|
58
|
+
const result = linkSkillToAgents({
|
|
59
|
+
skillName,
|
|
60
|
+
sourceDir,
|
|
61
|
+
linksDir: path.join(homedir, '.tank'),
|
|
62
|
+
source: 'dev',
|
|
63
|
+
homedir: options.homedir,
|
|
64
|
+
});
|
|
65
|
+
const agentNames = new Map(agents.map((agent) => [agent.id, agent.name]));
|
|
66
|
+
for (const agentId of result.linked) {
|
|
67
|
+
logger.success(agentNames.get(agentId) ?? agentId);
|
|
68
|
+
}
|
|
69
|
+
for (const agentId of result.skipped) {
|
|
70
|
+
const name = agentNames.get(agentId) ?? agentId;
|
|
71
|
+
logger.warn(`- ${name} (already linked)`);
|
|
72
|
+
}
|
|
73
|
+
for (const failure of result.failed) {
|
|
74
|
+
const name = agentNames.get(failure.agentId) ?? failure.agentId;
|
|
75
|
+
logger.error(`${name}: ${failure.error}`);
|
|
76
|
+
}
|
|
77
|
+
logger.success(`Linked ${skillName} to ${result.linked.length} agent(s)`);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=link.js.map
|