@chakresh/kresh 0.1.14 → 0.1.15
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/package.json +1 -1
- package/src/commands/install.js +16 -1
- package/src/services/filesystem.js +101 -87
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -60,8 +60,23 @@ export async function installSkill(skillSlug, isRetry = false) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const { agentType } = await inquirer.prompt([
|
|
64
|
+
{
|
|
65
|
+
type: 'list',
|
|
66
|
+
name: 'agentType',
|
|
67
|
+
message: 'Which AI Agent are you installing this skill for?',
|
|
68
|
+
choices: [
|
|
69
|
+
{ name: 'Antigravity (.agents/skills)', value: '.agents/skills' },
|
|
70
|
+
{ name: 'Claude Code (.claude/skills)', value: '.claude/skills' },
|
|
71
|
+
{ name: 'Codex (.codex/skills)', value: '.codex/skills' },
|
|
72
|
+
{ name: 'Cursor (.cursor/skills)', value: '.cursor/skills' },
|
|
73
|
+
{ name: 'Standard / Other (skills)', value: 'skills' }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
|
|
63
78
|
spinner.start('Writing skill files locally...');
|
|
64
|
-
const savedDir = await writeLocalSkill(skillSlug, skillContent, metadata);
|
|
79
|
+
const savedDir = await writeLocalSkill(skillSlug, skillContent, metadata, agentType);
|
|
65
80
|
|
|
66
81
|
spinner.succeed(`Successfully installed ${logger.bold(metadata.name)} (v${metadata.currentVersion}) by @${metadata.ownerUsername}`);
|
|
67
82
|
logger.info(`Saved to: ${logger.bold(savedDir)}`);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
|
+
const BASE_DIRS = ['skills', '.agents/skills', '.claude/skills', '.codex/skills', '.cursor/skills'];
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Helper to find the workspace or project root directory by scanning up.
|
|
6
8
|
*/
|
|
@@ -49,140 +51,152 @@ export async function getWorkspaceRoot(startDir = process.cwd()) {
|
|
|
49
51
|
/**
|
|
50
52
|
* Writes the SKILL.md and metadata.json files directly in a folder named after the skill's slug (excluding @owner scope folder) inside the skills folder.
|
|
51
53
|
*/
|
|
52
|
-
export async function writeLocalSkill(slug, skillContent, metadata) {
|
|
54
|
+
export async function writeLocalSkill(slug, skillContent, metadata, baseDir = 'skills') {
|
|
53
55
|
try {
|
|
54
56
|
const rootDir = await getWorkspaceRoot();
|
|
55
57
|
const folderName = (slug || metadata.slug).split('/').pop();
|
|
56
|
-
const targetDir = path.join(rootDir,
|
|
58
|
+
const targetDir = path.join(rootDir, baseDir, folderName);
|
|
57
59
|
|
|
58
60
|
await fs.mkdir(targetDir, { recursive: true });
|
|
59
61
|
await fs.writeFile(path.join(targetDir, 'SKILL.md'), skillContent, 'utf8');
|
|
60
62
|
await fs.writeFile(path.join(targetDir, 'metadata.json'), JSON.stringify(metadata, null, 2), 'utf8');
|
|
61
63
|
return targetDir;
|
|
62
64
|
} catch (error) {
|
|
63
|
-
console.error(
|
|
65
|
+
console.error(`Failed to write skill files in the ${baseDir} directory:`, error);
|
|
64
66
|
throw new Error(`Local file system write failed: ${error.message}`);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
/**
|
|
69
|
-
* Reads the local metadata.json of a specific skill if it exists.
|
|
70
|
-
*/
|
|
71
70
|
export async function readLocalSkillMetadata(slug) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const data = await fs.readFile(metadataPath, 'utf8');
|
|
77
|
-
return JSON.parse(data);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
// Also try legacy path for backwards compatibility
|
|
71
|
+
const rootDir = await getWorkspaceRoot();
|
|
72
|
+
const folderName = slug.split('/').pop();
|
|
73
|
+
|
|
74
|
+
for (const baseDir of BASE_DIRS) {
|
|
80
75
|
try {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const data = await fs.readFile(legacyPath, 'utf8');
|
|
76
|
+
const metadataPath = path.join(rootDir, baseDir, folderName, 'metadata.json');
|
|
77
|
+
const data = await fs.readFile(metadataPath, 'utf8');
|
|
84
78
|
return JSON.parse(data);
|
|
85
|
-
} catch (
|
|
86
|
-
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// Also try legacy path for backwards compatibility
|
|
81
|
+
try {
|
|
82
|
+
const legacyPath = path.join(rootDir, baseDir, slug, 'metadata.json');
|
|
83
|
+
const data = await fs.readFile(legacyPath, 'utf8');
|
|
84
|
+
return JSON.parse(data);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Continue to the next baseDir
|
|
87
|
+
}
|
|
87
88
|
}
|
|
88
89
|
}
|
|
90
|
+
return null;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
/**
|
|
92
|
-
* Removes the skill directory.
|
|
93
|
-
*/
|
|
94
93
|
export async function removeLocalSkill(slug) {
|
|
95
94
|
try {
|
|
96
95
|
const rootDir = await getWorkspaceRoot();
|
|
97
96
|
const folderName = slug.split('/').pop();
|
|
98
|
-
const targetDir = path.join(rootDir, 'skills', folderName);
|
|
99
|
-
await fs.rm(targetDir, { recursive: true, force: true });
|
|
100
|
-
|
|
101
|
-
// Also clean up legacy directory if it exists
|
|
102
|
-
const legacyDir = path.join(rootDir, 'skills', slug);
|
|
103
|
-
await fs.rm(legacyDir, { recursive: true, force: true });
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
98
|
+
for (const baseDir of BASE_DIRS) {
|
|
99
|
+
const targetDir = path.join(rootDir, baseDir, folderName);
|
|
100
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
101
|
+
|
|
102
|
+
// Also clean up legacy directory if it exists
|
|
103
|
+
const legacyDir = path.join(rootDir, baseDir, slug);
|
|
104
|
+
await fs.rm(legacyDir, { recursive: true, force: true });
|
|
105
|
+
|
|
106
|
+
// Clean up empty parent directory (e.g. @username) if it becomes empty
|
|
107
|
+
if (slug.includes('/')) {
|
|
108
|
+
const parts = slug.split('/');
|
|
109
|
+
const parentDir = path.join(rootDir, baseDir, parts[0]);
|
|
110
|
+
try {
|
|
111
|
+
const files = await fs.readdir(parentDir);
|
|
112
|
+
if (files.length === 0) {
|
|
113
|
+
await fs.rmdir(parentDir);
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// Ignore
|
|
113
117
|
}
|
|
114
|
-
} catch (e) {
|
|
115
|
-
// Ignore
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
return true;
|
|
119
121
|
} catch (error) {
|
|
120
|
-
console.error('Failed to remove skill files from the skills
|
|
122
|
+
console.error('Failed to remove skill files from the skills directories:', error);
|
|
121
123
|
throw new Error(`Local file system removal failed: ${error.message}`);
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
/**
|
|
126
|
-
* Lists all installed skills by inspecting the skills directory.
|
|
127
|
-
*/
|
|
128
127
|
export async function listLocalSkills() {
|
|
129
128
|
try {
|
|
130
129
|
const rootDir = await getWorkspaceRoot();
|
|
131
|
-
const skillsDir = path.join(rootDir, 'skills');
|
|
132
|
-
let entries = [];
|
|
133
|
-
try {
|
|
134
|
-
entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
135
|
-
} catch (e) {
|
|
136
|
-
if (e.code === 'ENOENT') return [];
|
|
137
|
-
throw e;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
130
|
const skills = [];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
131
|
+
const seenSlugs = new Set();
|
|
132
|
+
|
|
133
|
+
for (const baseDir of BASE_DIRS) {
|
|
134
|
+
const skillsDir = path.join(rootDir, baseDir);
|
|
135
|
+
let entries = [];
|
|
136
|
+
try {
|
|
137
|
+
entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
138
|
+
} catch (e) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (entry.isDirectory()) {
|
|
144
|
+
if (entry.name.startsWith('@')) {
|
|
145
|
+
// Legacy support: It's a user scope directory, scan subdirectories inside it
|
|
146
|
+
const scopeDir = path.join(skillsDir, entry.name);
|
|
147
|
+
let subEntries = [];
|
|
148
|
+
try {
|
|
149
|
+
subEntries = await fs.readdir(scopeDir, { withFileTypes: true });
|
|
150
|
+
} catch (e) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const subEntry of subEntries) {
|
|
155
|
+
if (subEntry.isDirectory()) {
|
|
156
|
+
const scopedSlug = `${entry.name}/${subEntry.name}`;
|
|
157
|
+
if (seenSlugs.has(scopedSlug)) continue;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const metadataPath = path.join(skillsDir, entry.name, subEntry.name, 'metadata.json');
|
|
161
|
+
const data = await fs.readFile(metadataPath, 'utf8');
|
|
162
|
+
const metadata = JSON.parse(data);
|
|
163
|
+
if (metadata) {
|
|
164
|
+
skills.push({
|
|
165
|
+
slug: metadata.slug || scopedSlug,
|
|
166
|
+
installed: true,
|
|
167
|
+
version: metadata.version || metadata.currentVersion || 'unknown',
|
|
168
|
+
name: metadata.name || 'unknown',
|
|
169
|
+
description: metadata.description || ''
|
|
170
|
+
});
|
|
171
|
+
seenSlugs.add(scopedSlug);
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
// Ignore
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
// New format: Directly under skills/
|
|
180
|
+
try {
|
|
181
|
+
const metadataPath = path.join(skillsDir, entry.name, 'metadata.json');
|
|
182
|
+
const data = await fs.readFile(metadataPath, 'utf8');
|
|
183
|
+
const metadata = JSON.parse(data);
|
|
184
|
+
const slug = metadata.slug || entry.name;
|
|
185
|
+
|
|
186
|
+
if (metadata && !seenSlugs.has(slug)) {
|
|
158
187
|
skills.push({
|
|
159
|
-
slug:
|
|
188
|
+
slug: slug,
|
|
160
189
|
installed: true,
|
|
161
190
|
version: metadata.version || metadata.currentVersion || 'unknown',
|
|
162
191
|
name: metadata.name || 'unknown',
|
|
163
192
|
description: metadata.description || ''
|
|
164
193
|
});
|
|
194
|
+
seenSlugs.add(slug);
|
|
165
195
|
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// Ignore if metadata is invalid/missing
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
|
-
} else {
|
|
169
|
-
// New format: Directly under skills/
|
|
170
|
-
try {
|
|
171
|
-
const metadataPath = path.join(skillsDir, entry.name, 'metadata.json');
|
|
172
|
-
const data = await fs.readFile(metadataPath, 'utf8');
|
|
173
|
-
const metadata = JSON.parse(data);
|
|
174
|
-
if (metadata) {
|
|
175
|
-
skills.push({
|
|
176
|
-
slug: metadata.slug || entry.name,
|
|
177
|
-
installed: true,
|
|
178
|
-
version: metadata.version || metadata.currentVersion || 'unknown',
|
|
179
|
-
name: metadata.name || 'unknown',
|
|
180
|
-
description: metadata.description || ''
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
} catch (e) {
|
|
184
|
-
// Ignore if metadata is invalid/missing
|
|
185
|
-
}
|
|
186
200
|
}
|
|
187
201
|
}
|
|
188
202
|
}
|