@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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin/tank.d.ts +2 -0
  3. package/dist/bin/tank.js +279 -0
  4. package/dist/bin/tank.js.map +1 -0
  5. package/dist/commands/audit.d.ts +5 -0
  6. package/dist/commands/audit.js +185 -0
  7. package/dist/commands/audit.js.map +1 -0
  8. package/dist/commands/doctor.d.ts +5 -0
  9. package/dist/commands/doctor.js +164 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/info.d.ts +5 -0
  12. package/dist/commands/info.js +102 -0
  13. package/dist/commands/info.js.map +1 -0
  14. package/dist/commands/init.d.ts +1 -0
  15. package/dist/commands/init.js +92 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/install.d.ts +39 -0
  18. package/dist/commands/install.js +550 -0
  19. package/dist/commands/install.js.map +1 -0
  20. package/dist/commands/link.d.ts +5 -0
  21. package/dist/commands/link.js +79 -0
  22. package/dist/commands/link.js.map +1 -0
  23. package/dist/commands/login.d.ts +14 -0
  24. package/dist/commands/login.js +87 -0
  25. package/dist/commands/login.js.map +1 -0
  26. package/dist/commands/logout.d.ts +9 -0
  27. package/dist/commands/logout.js +20 -0
  28. package/dist/commands/logout.js.map +1 -0
  29. package/dist/commands/permissions.d.ts +4 -0
  30. package/dist/commands/permissions.js +199 -0
  31. package/dist/commands/permissions.js.map +1 -0
  32. package/dist/commands/publish.d.ts +25 -0
  33. package/dist/commands/publish.js +166 -0
  34. package/dist/commands/publish.js.map +1 -0
  35. package/dist/commands/remove.d.ts +7 -0
  36. package/dist/commands/remove.js +163 -0
  37. package/dist/commands/remove.js.map +1 -0
  38. package/dist/commands/search.d.ts +5 -0
  39. package/dist/commands/search.js +67 -0
  40. package/dist/commands/search.js.map +1 -0
  41. package/dist/commands/unlink.d.ts +5 -0
  42. package/dist/commands/unlink.js +42 -0
  43. package/dist/commands/unlink.js.map +1 -0
  44. package/dist/commands/update.d.ts +8 -0
  45. package/dist/commands/update.js +337 -0
  46. package/dist/commands/update.js.map +1 -0
  47. package/dist/commands/upgrade.d.ts +6 -0
  48. package/dist/commands/upgrade.js +100 -0
  49. package/dist/commands/upgrade.js.map +1 -0
  50. package/dist/commands/verify.d.ts +22 -0
  51. package/dist/commands/verify.js +63 -0
  52. package/dist/commands/verify.js.map +1 -0
  53. package/dist/commands/whoami.d.ts +4 -0
  54. package/dist/commands/whoami.js +57 -0
  55. package/dist/commands/whoami.js.map +1 -0
  56. package/dist/index.d.ts +5 -0
  57. package/dist/index.js +5 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/lib/agents.d.ts +19 -0
  60. package/dist/lib/agents.js +84 -0
  61. package/dist/lib/agents.js.map +1 -0
  62. package/dist/lib/api-client.d.ts +14 -0
  63. package/dist/lib/api-client.js +63 -0
  64. package/dist/lib/api-client.js.map +1 -0
  65. package/dist/lib/config.d.ts +29 -0
  66. package/dist/lib/config.js +66 -0
  67. package/dist/lib/config.js.map +1 -0
  68. package/dist/lib/debug-logger.d.ts +9 -0
  69. package/dist/lib/debug-logger.js +77 -0
  70. package/dist/lib/debug-logger.js.map +1 -0
  71. package/dist/lib/frontmatter.d.ts +11 -0
  72. package/dist/lib/frontmatter.js +89 -0
  73. package/dist/lib/frontmatter.js.map +1 -0
  74. package/dist/lib/linker.d.ts +45 -0
  75. package/dist/lib/linker.js +137 -0
  76. package/dist/lib/linker.js.map +1 -0
  77. package/dist/lib/links.d.ts +20 -0
  78. package/dist/lib/links.js +105 -0
  79. package/dist/lib/links.js.map +1 -0
  80. package/dist/lib/lockfile.d.ts +24 -0
  81. package/dist/lib/lockfile.js +135 -0
  82. package/dist/lib/lockfile.js.map +1 -0
  83. package/dist/lib/logger.d.ts +6 -0
  84. package/dist/lib/logger.js +8 -0
  85. package/dist/lib/logger.js.map +1 -0
  86. package/dist/lib/packer.d.ts +21 -0
  87. package/dist/lib/packer.js +210 -0
  88. package/dist/lib/packer.js.map +1 -0
  89. package/dist/lib/upgrade-check.d.ts +1 -0
  90. package/dist/lib/upgrade-check.js +52 -0
  91. package/dist/lib/upgrade-check.js.map +1 -0
  92. package/dist/version.d.ts +2 -0
  93. package/dist/version.js +4 -0
  94. package/dist/version.js.map +1 -0
  95. 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":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAmB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAS/F,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAsB;IACxD,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAErE,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,YAAY,GAAG,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;QAE7C,gDAAgD;QAChD,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,qBAAqB,CAAC;gBACzC,SAAS,EAAE,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;gBAC1C,OAAO;aACR,CAAC,CAAC;YACH,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrC,MAAM,CAAC,IAAI,CAAC,iBAAiB,YAAY,CAAC,QAAQ,CAAC,MAAM,WAAW,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;QACrD,CAAC;QAED,qCAAqC;QACrC,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,uBAAuB,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,CAAC;QACpF,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACjC,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,8BAA8B;QAC9B,MAAM,QAAQ,GAAG,iBAAiB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;QACvD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QACjE,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,IAAI,IAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC/C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,GAAG,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;YAC3D,CAAC;YAED,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBACpC,IAAI,MAAM,IAAI,CAAC;oBAAE,SAAS;gBAC1B,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;gBACrC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;oBACrB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;YAED,MAAM,YAAY,GAA4B,EAAE,CAAC;YACjD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;gBAClD,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvC,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,YAAoC,CAAC;YAEnD,EAAE,CAAC,aAAa,CACd,QAAQ,EACR,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACrC,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,OAAO,CAAC,WAAW,IAAI,WAAW,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,sBAAsB;IACtB,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAC3D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CACb,2BAA2B,SAAS,kBAAkB,CACvD,CAAC;IACJ,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,yCAAyC;IACzC,MAAM,MAAM,GAAG,CAAC,UAAU,CAAC,MAAM,IAAI,EAAE,CAA2B,CAAC;IACnE,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,UAAU,IAAI,+CAA+C,CAAC,CAAC;IACjF,CAAC;IAED,mCAAmC;IACnC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;IAE3B,+BAA+B;IAC/B,EAAE,CAAC,aAAa,CACd,cAAc,EACd,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAC3C,CAAC;IAEF,iCAAiC;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACrD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,IAAgB,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAC3D,CAAC;QAED,qDAAqD;QACrD,wEAAwE;QACxE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,MAAM,IAAI,CAAC;gBAAE,SAAS;YAC1B,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YACrC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,+DAA+D;QAC/D,MAAM,YAAY,GAA4B,EAAE,CAAC;QACjD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAClD,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,YAAoC,CAAC;QAEnD,EAAE,CAAC,aAAa,CACd,QAAQ,EACR,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACrC,CAAC;IACJ,CAAC;IAED,kDAAkD;IAClD,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,qBAAqB,CAAC;YACzC,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC;YACvC,OAAO;SACR,CAAC,CAAC;QACH,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,iBAAiB,YAAY,CAAC,QAAQ,CAAC,MAAM,WAAW,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IACrD,CAAC;IAED,uCAAuC;IACvC,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;IACjF,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACjC,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,2CAA2C;IAC3C,MAAM,QAAQ,GAAG,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC9C,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,mBAAmB;IACnB,MAAM,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,WAAW,CAAC,UAAkB,EAAE,SAAiB;IACxD,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAe,EAAE,SAAiB;IAC3D,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface SearchOptions {
2
+ query: string;
3
+ configDir?: string;
4
+ }
5
+ export declare function searchCommand(options: SearchOptions): Promise<void>;
@@ -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,5 @@
1
+ export interface UnlinkOptions {
2
+ directory?: string;
3
+ homedir?: string;
4
+ }
5
+ export declare function unlinkCommand(options?: UnlinkOptions): Promise<void>;
@@ -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,8 @@
1
+ export interface UpdateOptions {
2
+ name?: string;
3
+ directory?: string;
4
+ configDir?: string;
5
+ global?: boolean;
6
+ homedir?: string;
7
+ }
8
+ export declare function updateCommand(options: UpdateOptions): Promise<void>;
@@ -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