@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,163 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { LOCKFILE_VERSION } from '@tank/shared';
|
|
5
|
+
import { logger } from '../lib/logger.js';
|
|
6
|
+
import { unlinkSkillFromAgents } from '../lib/linker.js';
|
|
7
|
+
import { getSymlinkName, getGlobalSkillsDir, getGlobalAgentSkillsDir } from '../lib/agents.js';
|
|
8
|
+
export async function removeCommand(options) {
|
|
9
|
+
const { name, directory = process.cwd(), global, homedir } = options;
|
|
10
|
+
if (global) {
|
|
11
|
+
const resolvedHome = homedir ?? os.homedir();
|
|
12
|
+
// 1. Unlink from agents (failures are warnings)
|
|
13
|
+
try {
|
|
14
|
+
const unlinkResult = unlinkSkillFromAgents({
|
|
15
|
+
skillName: name,
|
|
16
|
+
linksDir: path.join(resolvedHome, '.tank'),
|
|
17
|
+
homedir,
|
|
18
|
+
});
|
|
19
|
+
if (unlinkResult.unlinked.length > 0) {
|
|
20
|
+
logger.info(`Unlinked from ${unlinkResult.unlinked.length} agent(s)`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
logger.warn('Agent unlinking skipped (non-fatal)');
|
|
25
|
+
}
|
|
26
|
+
// 2. Remove agent-skills wrapper dir
|
|
27
|
+
const symlinkName = getSymlinkName(name);
|
|
28
|
+
const agentSkillDir = path.join(getGlobalAgentSkillsDir(resolvedHome), symlinkName);
|
|
29
|
+
if (fs.existsSync(agentSkillDir)) {
|
|
30
|
+
fs.rmSync(agentSkillDir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
// 3. Remove global skills dir
|
|
33
|
+
const skillDir = getGlobalSkillDir(resolvedHome, name);
|
|
34
|
+
if (fs.existsSync(skillDir)) {
|
|
35
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
// 4. Update global lockfile
|
|
38
|
+
const lockPath = path.join(resolvedHome, '.tank', 'skills.lock');
|
|
39
|
+
if (fs.existsSync(lockPath)) {
|
|
40
|
+
let lock;
|
|
41
|
+
try {
|
|
42
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
43
|
+
lock = JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
lock = { lockfileVersion: LOCKFILE_VERSION, skills: {} };
|
|
47
|
+
}
|
|
48
|
+
for (const key of Object.keys(lock.skills)) {
|
|
49
|
+
const lastAt = key.lastIndexOf('@');
|
|
50
|
+
if (lastAt <= 0)
|
|
51
|
+
continue;
|
|
52
|
+
const keyName = key.slice(0, lastAt);
|
|
53
|
+
if (keyName === name) {
|
|
54
|
+
delete lock.skills[key];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const sortedSkills = {};
|
|
58
|
+
for (const key of Object.keys(lock.skills).sort()) {
|
|
59
|
+
sortedSkills[key] = lock.skills[key];
|
|
60
|
+
}
|
|
61
|
+
lock.skills = sortedSkills;
|
|
62
|
+
fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
63
|
+
}
|
|
64
|
+
logger.success(`Removed ${name} (global)`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// 1. Read skills.json
|
|
68
|
+
const skillsJsonPath = path.join(directory, 'skills.json');
|
|
69
|
+
if (!fs.existsSync(skillsJsonPath)) {
|
|
70
|
+
throw new Error(`No skills.json found in ${directory}. Run: tank init`);
|
|
71
|
+
}
|
|
72
|
+
let skillsJson;
|
|
73
|
+
try {
|
|
74
|
+
const raw = fs.readFileSync(skillsJsonPath, 'utf-8');
|
|
75
|
+
skillsJson = JSON.parse(raw);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new Error('Failed to read or parse skills.json');
|
|
79
|
+
}
|
|
80
|
+
// 2. Check if skill exists in skills map
|
|
81
|
+
const skills = (skillsJson.skills ?? {});
|
|
82
|
+
if (!(name in skills)) {
|
|
83
|
+
throw new Error(`Skill "${name}" is not installed (not found in skills.json)`);
|
|
84
|
+
}
|
|
85
|
+
// 3. Remove skill from skills.json
|
|
86
|
+
delete skills[name];
|
|
87
|
+
skillsJson.skills = skills;
|
|
88
|
+
// 4. Write updated skills.json
|
|
89
|
+
fs.writeFileSync(skillsJsonPath, JSON.stringify(skillsJson, null, 2) + '\n');
|
|
90
|
+
// 5. Read skills.lock if present
|
|
91
|
+
const lockPath = path.join(directory, 'skills.lock');
|
|
92
|
+
if (fs.existsSync(lockPath)) {
|
|
93
|
+
let lock;
|
|
94
|
+
try {
|
|
95
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
96
|
+
lock = JSON.parse(raw);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
lock = { lockfileVersion: LOCKFILE_VERSION, skills: {} };
|
|
100
|
+
}
|
|
101
|
+
// 6. Remove ALL lockfile entries for this skill name
|
|
102
|
+
// Key format: name@version — use lastIndexOf('@') to split scoped names
|
|
103
|
+
for (const key of Object.keys(lock.skills)) {
|
|
104
|
+
const lastAt = key.lastIndexOf('@');
|
|
105
|
+
if (lastAt <= 0)
|
|
106
|
+
continue;
|
|
107
|
+
const keyName = key.slice(0, lastAt);
|
|
108
|
+
if (keyName === name) {
|
|
109
|
+
delete lock.skills[key];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// 7. Write updated skills.lock (sorted keys, trailing newline)
|
|
113
|
+
const sortedSkills = {};
|
|
114
|
+
for (const key of Object.keys(lock.skills).sort()) {
|
|
115
|
+
sortedSkills[key] = lock.skills[key];
|
|
116
|
+
}
|
|
117
|
+
lock.skills = sortedSkills;
|
|
118
|
+
fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
119
|
+
}
|
|
120
|
+
// 7.5. Unlink from agents (failures are warnings)
|
|
121
|
+
try {
|
|
122
|
+
const unlinkResult = unlinkSkillFromAgents({
|
|
123
|
+
skillName: name,
|
|
124
|
+
linksDir: path.join(directory, '.tank'),
|
|
125
|
+
homedir,
|
|
126
|
+
});
|
|
127
|
+
if (unlinkResult.unlinked.length > 0) {
|
|
128
|
+
logger.info(`Unlinked from ${unlinkResult.unlinked.length} agent(s)`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
logger.warn('Agent unlinking skipped (non-fatal)');
|
|
133
|
+
}
|
|
134
|
+
// 7.6. Remove agent-skills wrapper dir
|
|
135
|
+
const symlinkName = getSymlinkName(name);
|
|
136
|
+
const agentSkillDir = path.join(directory, '.tank', 'agent-skills', symlinkName);
|
|
137
|
+
if (fs.existsSync(agentSkillDir)) {
|
|
138
|
+
fs.rmSync(agentSkillDir, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
// 8. Delete .tank/skills/{name}/ directory
|
|
141
|
+
const skillDir = getSkillDir(directory, name);
|
|
142
|
+
if (fs.existsSync(skillDir)) {
|
|
143
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
// 9. Print success
|
|
146
|
+
logger.success(`Removed ${name}`);
|
|
147
|
+
}
|
|
148
|
+
function getSkillDir(projectDir, skillName) {
|
|
149
|
+
if (skillName.startsWith('@')) {
|
|
150
|
+
const [scope, name] = skillName.split('/');
|
|
151
|
+
return path.join(projectDir, '.tank', 'skills', scope, name);
|
|
152
|
+
}
|
|
153
|
+
return path.join(projectDir, '.tank', 'skills', skillName);
|
|
154
|
+
}
|
|
155
|
+
function getGlobalSkillDir(homedir, skillName) {
|
|
156
|
+
const globalDir = getGlobalSkillsDir(homedir);
|
|
157
|
+
if (skillName.startsWith('@')) {
|
|
158
|
+
const [scope, name] = skillName.split('/');
|
|
159
|
+
return path.join(globalDir, scope, name);
|
|
160
|
+
}
|
|
161
|
+
return path.join(globalDir, skillName);
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=remove.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remove.js","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getConfig } from '../lib/config.js';
|
|
3
|
+
import { USER_AGENT } from '../version.js';
|
|
4
|
+
const MAX_DESC_LENGTH = 60;
|
|
5
|
+
function scoreColor(score) {
|
|
6
|
+
if (score >= 7)
|
|
7
|
+
return chalk.green;
|
|
8
|
+
if (score >= 4)
|
|
9
|
+
return chalk.yellow;
|
|
10
|
+
return chalk.red;
|
|
11
|
+
}
|
|
12
|
+
function truncate(text, maxLen) {
|
|
13
|
+
if (text.length <= maxLen)
|
|
14
|
+
return text;
|
|
15
|
+
return text.slice(0, maxLen - 3) + '...';
|
|
16
|
+
}
|
|
17
|
+
function padRight(text, width) {
|
|
18
|
+
if (text.length >= width)
|
|
19
|
+
return text;
|
|
20
|
+
return text + ' '.repeat(width - text.length);
|
|
21
|
+
}
|
|
22
|
+
export async function searchCommand(options) {
|
|
23
|
+
const { query, configDir } = options;
|
|
24
|
+
const config = getConfig(configDir);
|
|
25
|
+
const url = `${config.registry}/api/v1/search?q=${encodeURIComponent(query)}&limit=20`;
|
|
26
|
+
let res;
|
|
27
|
+
try {
|
|
28
|
+
const headers = { 'User-Agent': USER_AGENT };
|
|
29
|
+
if (config.token) {
|
|
30
|
+
headers.Authorization = `Bearer ${config.token}`;
|
|
31
|
+
}
|
|
32
|
+
res = await fetch(url, {
|
|
33
|
+
headers,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
throw new Error(`Network error searching: ${err instanceof Error ? err.message : String(err)}`);
|
|
38
|
+
}
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const body = await res.json().catch(() => ({}));
|
|
41
|
+
throw new Error(body.error ?? `Search failed: ${res.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
if (data.results.length === 0) {
|
|
45
|
+
console.log(`No skills found for "${query}"`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Print table header
|
|
49
|
+
console.log(padRight('NAME', 30) +
|
|
50
|
+
padRight('VERSION', 10) +
|
|
51
|
+
padRight('SCORE', 8) +
|
|
52
|
+
'DESCRIPTION');
|
|
53
|
+
// Print each result
|
|
54
|
+
for (const result of data.results) {
|
|
55
|
+
const name = chalk.bold(padRight(result.name, 30));
|
|
56
|
+
const version = padRight(result.latestVersion, 10);
|
|
57
|
+
const scoreStr = Number.isInteger(result.auditScore)
|
|
58
|
+
? result.auditScore.toFixed(1)
|
|
59
|
+
: String(result.auditScore);
|
|
60
|
+
const score = scoreColor(result.auditScore)(padRight(scoreStr, 8));
|
|
61
|
+
const desc = truncate(result.description ?? '', MAX_DESC_LENGTH);
|
|
62
|
+
console.log(`${name}${version}${score}${desc}`);
|
|
63
|
+
}
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(`${data.results.length} skill${data.results.length === 1 ? '' : 's'} found`);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAuB3C,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC;IACnC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IACpC,OAAO,KAAK,CAAC,GAAG,CAAC;AACnB,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;AAC3C,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAa;IAC3C,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAsB;IACxD,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IACrC,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAEpC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,QAAQ,oBAAoB,kBAAkB,CAAC,KAAK,CAAC,WAAW,CAAC;IAEvF,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,MAAM,OAAO,GAA2B,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;QACrE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,aAAa,GAAG,UAAU,MAAM,CAAC,KAAK,EAAE,CAAC;QACnD,CAAC;QACD,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACrB,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAuB,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,kBAAkB,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAoB,CAAC;IAEhD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,GAAG,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,qBAAqB;IACrB,OAAO,CAAC,GAAG,CACT,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACpB,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACpB,aAAa,CACd,CAAC;IAEF,oBAAoB;IACpB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC;YAClD,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;YAC9B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC;QAEjE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,SAAS,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;AAC3F,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { getGlobalAgentSkillsDir, getSymlinkName } from '../lib/agents.js';
|
|
5
|
+
import { unlinkSkillFromAgents } from '../lib/linker.js';
|
|
6
|
+
import { logger } from '../lib/logger.js';
|
|
7
|
+
export async function unlinkCommand(options = {}) {
|
|
8
|
+
const workDir = options.directory ?? process.cwd();
|
|
9
|
+
const skillsJsonPath = path.join(workDir, 'skills.json');
|
|
10
|
+
if (!fs.existsSync(skillsJsonPath)) {
|
|
11
|
+
throw new Error('No skills.json found. Run this command from a skill directory.');
|
|
12
|
+
}
|
|
13
|
+
let skillsJson;
|
|
14
|
+
try {
|
|
15
|
+
const raw = fs.readFileSync(skillsJsonPath, 'utf-8');
|
|
16
|
+
skillsJson = JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error('Failed to read or parse skills.json');
|
|
20
|
+
}
|
|
21
|
+
const skillName = skillsJson.name;
|
|
22
|
+
if (typeof skillName !== 'string' || skillName.trim().length === 0) {
|
|
23
|
+
throw new Error("Missing 'name' in skills.json");
|
|
24
|
+
}
|
|
25
|
+
const homedir = options.homedir ?? os.homedir();
|
|
26
|
+
const result = unlinkSkillFromAgents({
|
|
27
|
+
skillName,
|
|
28
|
+
linksDir: path.join(homedir, '.tank'),
|
|
29
|
+
homedir: options.homedir,
|
|
30
|
+
});
|
|
31
|
+
const symlinkName = getSymlinkName(skillName);
|
|
32
|
+
const wrapperDir = path.join(getGlobalAgentSkillsDir(options.homedir), symlinkName);
|
|
33
|
+
if (fs.existsSync(wrapperDir)) {
|
|
34
|
+
fs.rmSync(wrapperDir, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
if (result.unlinked.length === 0 && result.notFound.length === 0) {
|
|
37
|
+
logger.info(`No links found for ${skillName}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
logger.success(`Unlinked ${skillName} from ${result.unlinked.length} agent(s)`);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=unlink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unlink.js","sourceRoot":"","sources":["../../src/commands/unlink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAO1C,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAyB,EAAE;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACnD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAEzD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IAED,IAAI,UAAmC,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QACrD,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC;IAClC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,qBAAqB,CAAC;QACnC,SAAS;QACT,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC;QACrC,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC;IACpF,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,YAAY,SAAS,SAAS,MAAM,CAAC,QAAQ,CAAC,MAAM,WAAW,CAAC,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolve } from '@tank/shared';
|
|
5
|
+
import { getConfig } from '../lib/config.js';
|
|
6
|
+
import { logger } from '../lib/logger.js';
|
|
7
|
+
import { getGlobalSkillsDir } from '../lib/agents.js';
|
|
8
|
+
import { installCommand } from './install.js';
|
|
9
|
+
import { USER_AGENT } from '../version.js';
|
|
10
|
+
export async function updateCommand(options) {
|
|
11
|
+
const { name, directory = process.cwd(), configDir, global = false, homedir, } = options;
|
|
12
|
+
if (global) {
|
|
13
|
+
if (name) {
|
|
14
|
+
await updateSingleGlobal(name, configDir, homedir);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
await updateAllGlobal(configDir, homedir);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// 1. Read skills.json
|
|
22
|
+
const skillsJsonPath = path.join(directory, 'skills.json');
|
|
23
|
+
if (!fs.existsSync(skillsJsonPath)) {
|
|
24
|
+
throw new Error(`No skills.json found in ${directory}. Run: tank init`);
|
|
25
|
+
}
|
|
26
|
+
let skillsJson;
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(skillsJsonPath, 'utf-8');
|
|
29
|
+
skillsJson = JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new Error('Failed to read or parse skills.json');
|
|
33
|
+
}
|
|
34
|
+
const skills = (skillsJson.skills ?? {});
|
|
35
|
+
if (name) {
|
|
36
|
+
// Update single skill
|
|
37
|
+
await updateSingle(name, skills, directory, configDir, global, homedir);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Update all skills
|
|
41
|
+
await updateAll(skills, directory, configDir, global, homedir);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function parseLockKey(key) {
|
|
45
|
+
const lastAt = key.lastIndexOf('@');
|
|
46
|
+
if (lastAt <= 0)
|
|
47
|
+
return null;
|
|
48
|
+
return { name: key.slice(0, lastAt), version: key.slice(lastAt + 1) };
|
|
49
|
+
}
|
|
50
|
+
function readLockfile(lockPath) {
|
|
51
|
+
if (!fs.existsSync(lockPath)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
56
|
+
return JSON.parse(raw);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readLockfileStrict(lockPath) {
|
|
63
|
+
if (!fs.existsSync(lockPath)) {
|
|
64
|
+
throw new Error(`Global skills.lock not found at ${lockPath}`);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const raw = fs.readFileSync(lockPath, 'utf-8');
|
|
68
|
+
return JSON.parse(raw);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error('Failed to read or parse global skills.lock');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function getGlobalLockPath(homedir) {
|
|
75
|
+
const globalSkillsDir = getGlobalSkillsDir(homedir ?? os.homedir());
|
|
76
|
+
return path.join(path.dirname(globalSkillsDir), 'skills.lock');
|
|
77
|
+
}
|
|
78
|
+
async function updateSingle(name, skills, directory, configDir, global = false, homedir) {
|
|
79
|
+
// 2. Get version range from skills.json
|
|
80
|
+
const versionRange = skills[name];
|
|
81
|
+
if (!versionRange) {
|
|
82
|
+
throw new Error(`Skill "${name}" is not installed (not found in skills.json)`);
|
|
83
|
+
}
|
|
84
|
+
const config = getConfig(configDir);
|
|
85
|
+
const requestHeaders = { 'User-Agent': USER_AGENT };
|
|
86
|
+
if (config.token) {
|
|
87
|
+
requestHeaders.Authorization = `Bearer ${config.token}`;
|
|
88
|
+
}
|
|
89
|
+
// 3. Fetch available versions from registry
|
|
90
|
+
const encodedName = encodeURIComponent(name);
|
|
91
|
+
const versionsUrl = `${config.registry}/api/v1/skills/${encodedName}/versions`;
|
|
92
|
+
let versionsRes;
|
|
93
|
+
try {
|
|
94
|
+
versionsRes = await fetch(versionsUrl, {
|
|
95
|
+
headers: requestHeaders,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
throw new Error(`Network error fetching versions for ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
100
|
+
}
|
|
101
|
+
if (!versionsRes.ok) {
|
|
102
|
+
if (versionsRes.status === 404) {
|
|
103
|
+
throw new Error(`Skill not found in registry: ${name}`);
|
|
104
|
+
}
|
|
105
|
+
const body = await versionsRes.json().catch(() => ({}));
|
|
106
|
+
throw new Error(body.error ?? versionsRes.statusText);
|
|
107
|
+
}
|
|
108
|
+
const versionsData = await versionsRes.json();
|
|
109
|
+
const availableVersions = versionsData.versions.map((v) => v.version);
|
|
110
|
+
// 4. Resolve best version
|
|
111
|
+
const resolved = resolve(versionRange, availableVersions);
|
|
112
|
+
if (!resolved) {
|
|
113
|
+
throw new Error(`No version of ${name} satisfies range "${versionRange}". Available: ${availableVersions.join(', ')}`);
|
|
114
|
+
}
|
|
115
|
+
// 5. Read lockfile to find current installed version
|
|
116
|
+
const lockPath = global
|
|
117
|
+
? getGlobalLockPath(homedir)
|
|
118
|
+
: path.join(directory, 'skills.lock');
|
|
119
|
+
let currentVersion = null;
|
|
120
|
+
const lock = readLockfile(lockPath);
|
|
121
|
+
if (lock) {
|
|
122
|
+
for (const key of Object.keys(lock.skills)) {
|
|
123
|
+
const parsed = parseLockKey(key);
|
|
124
|
+
if (!parsed)
|
|
125
|
+
continue;
|
|
126
|
+
if (parsed.name === name) {
|
|
127
|
+
currentVersion = parsed.version;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// 6. If resolved === current, already at latest
|
|
133
|
+
if (resolved === currentVersion) {
|
|
134
|
+
logger.info(`Already at latest: ${name}@${resolved}`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// 7. Install the newer version
|
|
138
|
+
await installCommand({
|
|
139
|
+
name,
|
|
140
|
+
versionRange,
|
|
141
|
+
directory,
|
|
142
|
+
configDir,
|
|
143
|
+
global,
|
|
144
|
+
homedir,
|
|
145
|
+
});
|
|
146
|
+
logger.success(`Updated ${name} to ${resolved}`);
|
|
147
|
+
}
|
|
148
|
+
async function updateAll(skills, directory, configDir, global = false, homedir) {
|
|
149
|
+
const skillEntries = Object.entries(skills);
|
|
150
|
+
if (skillEntries.length === 0) {
|
|
151
|
+
logger.info('No skills defined in skills.json');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let updatedCount = 0;
|
|
155
|
+
for (const [name] of skillEntries) {
|
|
156
|
+
const config = getConfig(configDir);
|
|
157
|
+
const requestHeaders = { 'User-Agent': USER_AGENT };
|
|
158
|
+
if (config.token) {
|
|
159
|
+
requestHeaders.Authorization = `Bearer ${config.token}`;
|
|
160
|
+
}
|
|
161
|
+
const versionRange = skills[name];
|
|
162
|
+
// Fetch available versions
|
|
163
|
+
const encodedName = encodeURIComponent(name);
|
|
164
|
+
const versionsUrl = `${config.registry}/api/v1/skills/${encodedName}/versions`;
|
|
165
|
+
let versionsRes;
|
|
166
|
+
try {
|
|
167
|
+
versionsRes = await fetch(versionsUrl, {
|
|
168
|
+
headers: requestHeaders,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
throw new Error(`Network error fetching versions for ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
173
|
+
}
|
|
174
|
+
if (!versionsRes.ok) {
|
|
175
|
+
throw new Error(`Failed to fetch versions for ${name}: ${versionsRes.statusText}`);
|
|
176
|
+
}
|
|
177
|
+
const versionsData = await versionsRes.json();
|
|
178
|
+
const availableVersions = versionsData.versions.map((v) => v.version);
|
|
179
|
+
const resolved = resolve(versionRange, availableVersions);
|
|
180
|
+
if (!resolved)
|
|
181
|
+
continue;
|
|
182
|
+
// Check current version from lockfile
|
|
183
|
+
const lockPath = global
|
|
184
|
+
? getGlobalLockPath(homedir)
|
|
185
|
+
: path.join(directory, 'skills.lock');
|
|
186
|
+
let currentVersion = null;
|
|
187
|
+
const lock = readLockfile(lockPath);
|
|
188
|
+
if (lock) {
|
|
189
|
+
for (const key of Object.keys(lock.skills)) {
|
|
190
|
+
const parsed = parseLockKey(key);
|
|
191
|
+
if (!parsed)
|
|
192
|
+
continue;
|
|
193
|
+
if (parsed.name === name) {
|
|
194
|
+
currentVersion = parsed.version;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (resolved === currentVersion) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
await installCommand({
|
|
203
|
+
name,
|
|
204
|
+
versionRange,
|
|
205
|
+
directory,
|
|
206
|
+
configDir,
|
|
207
|
+
global,
|
|
208
|
+
homedir,
|
|
209
|
+
});
|
|
210
|
+
updatedCount++;
|
|
211
|
+
}
|
|
212
|
+
if (updatedCount === 0) {
|
|
213
|
+
logger.info('All skills up to date');
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
logger.success(`Updated ${updatedCount} skill${updatedCount === 1 ? '' : 's'}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function updateSingleGlobal(name, configDir, homedir) {
|
|
220
|
+
const lockPath = getGlobalLockPath(homedir);
|
|
221
|
+
const lock = readLockfileStrict(lockPath);
|
|
222
|
+
let currentVersion = null;
|
|
223
|
+
for (const key of Object.keys(lock.skills)) {
|
|
224
|
+
const parsed = parseLockKey(key);
|
|
225
|
+
if (!parsed)
|
|
226
|
+
continue;
|
|
227
|
+
if (parsed.name === name) {
|
|
228
|
+
currentVersion = parsed.version;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!currentVersion) {
|
|
233
|
+
throw new Error(`Skill "${name}" is not installed globally (not found in skills.lock)`);
|
|
234
|
+
}
|
|
235
|
+
const config = getConfig(configDir);
|
|
236
|
+
const requestHeaders = { 'User-Agent': USER_AGENT };
|
|
237
|
+
if (config.token) {
|
|
238
|
+
requestHeaders.Authorization = `Bearer ${config.token}`;
|
|
239
|
+
}
|
|
240
|
+
const encodedName = encodeURIComponent(name);
|
|
241
|
+
const versionsUrl = `${config.registry}/api/v1/skills/${encodedName}/versions`;
|
|
242
|
+
let versionsRes;
|
|
243
|
+
try {
|
|
244
|
+
versionsRes = await fetch(versionsUrl, {
|
|
245
|
+
headers: requestHeaders,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
throw new Error(`Network error fetching versions for ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
+
}
|
|
251
|
+
if (!versionsRes.ok) {
|
|
252
|
+
if (versionsRes.status === 404) {
|
|
253
|
+
throw new Error(`Skill not found in registry: ${name}`);
|
|
254
|
+
}
|
|
255
|
+
const body = await versionsRes.json().catch(() => ({}));
|
|
256
|
+
throw new Error(body.error ?? versionsRes.statusText);
|
|
257
|
+
}
|
|
258
|
+
const versionsData = await versionsRes.json();
|
|
259
|
+
const availableVersions = versionsData.versions.map((v) => v.version);
|
|
260
|
+
const versionRange = `>=${currentVersion}`;
|
|
261
|
+
const resolved = resolve(versionRange, availableVersions);
|
|
262
|
+
if (!resolved) {
|
|
263
|
+
throw new Error(`No version of ${name} satisfies range "${versionRange}". Available: ${availableVersions.join(', ')}`);
|
|
264
|
+
}
|
|
265
|
+
if (resolved === currentVersion) {
|
|
266
|
+
logger.info(`Already at latest: ${name}@${resolved}`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
await installCommand({
|
|
270
|
+
name,
|
|
271
|
+
versionRange,
|
|
272
|
+
global: true,
|
|
273
|
+
homedir,
|
|
274
|
+
configDir,
|
|
275
|
+
});
|
|
276
|
+
logger.success(`Updated ${name} to ${resolved}`);
|
|
277
|
+
}
|
|
278
|
+
async function updateAllGlobal(configDir, homedir) {
|
|
279
|
+
const lockPath = getGlobalLockPath(homedir);
|
|
280
|
+
const lock = readLockfileStrict(lockPath);
|
|
281
|
+
const entries = Object.keys(lock.skills);
|
|
282
|
+
if (entries.length === 0) {
|
|
283
|
+
logger.info('No skills defined in global skills.lock');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
let updatedCount = 0;
|
|
287
|
+
for (const key of entries) {
|
|
288
|
+
const parsed = parseLockKey(key);
|
|
289
|
+
if (!parsed)
|
|
290
|
+
continue;
|
|
291
|
+
const { name } = parsed;
|
|
292
|
+
const config = getConfig(configDir);
|
|
293
|
+
const requestHeaders = { 'User-Agent': USER_AGENT };
|
|
294
|
+
if (config.token) {
|
|
295
|
+
requestHeaders.Authorization = `Bearer ${config.token}`;
|
|
296
|
+
}
|
|
297
|
+
const versionRange = '*';
|
|
298
|
+
const encodedName = encodeURIComponent(name);
|
|
299
|
+
const versionsUrl = `${config.registry}/api/v1/skills/${encodedName}/versions`;
|
|
300
|
+
let versionsRes;
|
|
301
|
+
try {
|
|
302
|
+
versionsRes = await fetch(versionsUrl, {
|
|
303
|
+
headers: requestHeaders,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
throw new Error(`Network error fetching versions for ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
308
|
+
}
|
|
309
|
+
if (!versionsRes.ok) {
|
|
310
|
+
throw new Error(`Failed to fetch versions for ${name}: ${versionsRes.statusText}`);
|
|
311
|
+
}
|
|
312
|
+
const versionsData = await versionsRes.json();
|
|
313
|
+
const availableVersions = versionsData.versions.map((v) => v.version);
|
|
314
|
+
const resolved = resolve(versionRange, availableVersions);
|
|
315
|
+
if (!resolved)
|
|
316
|
+
continue;
|
|
317
|
+
const currentVersion = parsed.version;
|
|
318
|
+
if (resolved === currentVersion) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
await installCommand({
|
|
322
|
+
name,
|
|
323
|
+
versionRange,
|
|
324
|
+
global: true,
|
|
325
|
+
homedir,
|
|
326
|
+
configDir,
|
|
327
|
+
});
|
|
328
|
+
updatedCount++;
|
|
329
|
+
}
|
|
330
|
+
if (updatedCount === 0) {
|
|
331
|
+
logger.info('All skills up to date');
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
logger.success(`Updated ${updatedCount} skill${updatedCount === 1 ? '' : 's'}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=update.js.map
|