@eve-horizon/cli 0.2.11 → 0.2.13

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.
@@ -1,390 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.handleSkills = handleSkills;
37
- const fs = __importStar(require("node:fs"));
38
- const path = __importStar(require("node:path"));
39
- const node_child_process_1 = require("node:child_process");
40
- // ============================================================================
41
- // Main Handler
42
- // ============================================================================
43
- async function handleSkills(subcommand, positionals, flags) {
44
- switch (subcommand) {
45
- case 'install':
46
- return handleInstall(flags);
47
- default:
48
- throw new Error('Usage: eve skills <install>\n' +
49
- ' install Install skills from skills.txt using openskills --universal');
50
- }
51
- }
52
- // ============================================================================
53
- // Subcommand Handlers
54
- // ============================================================================
55
- /**
56
- * eve skills install [--skip-installed] [--no-sync] [--no-commit]
57
- * Install skills from skills.txt manifest using openskills --universal
58
- */
59
- async function handleInstall(flags) {
60
- const skipInstalled = Boolean(flags['skip-installed']);
61
- const sync = flags.sync !== false && flags['no-sync'] !== true;
62
- const commit = flags.commit !== false && flags['no-commit'] !== true;
63
- const projectRoot = process.cwd();
64
- // Check if openskills is available
65
- if (!commandExists('openskills')) {
66
- throw new Error('openskills CLI not found. Install it with:\n' +
67
- ' npm install -g @anthropic/openskills');
68
- }
69
- const manifestPath = path.join(projectRoot, 'skills.txt');
70
- const skillsDir = path.join(projectRoot, '.agent', 'skills');
71
- // Parse manifest
72
- const manifest = parseSkillsManifest(manifestPath);
73
- if (manifest.length === 0) {
74
- console.log('No skills.txt found or empty; nothing to install');
75
- return;
76
- }
77
- // Filter to skills that need installation
78
- const toInstall = skipInstalled
79
- ? manifest.filter((s) => !getInstalledSkills(skillsDir).has(s.name))
80
- : manifest;
81
- if (toInstall.length === 0) {
82
- console.log('All skills already installed (use without --skip-installed to update)');
83
- }
84
- else {
85
- console.log(`Installing ${toInstall.length} skill(s)...`);
86
- for (const skill of toInstall) {
87
- console.log(` Installing: ${skill.source} (${skill.type})`);
88
- try {
89
- (0, node_child_process_1.execSync)(`openskills install ${JSON.stringify(skill.source)} --universal --yes`, {
90
- cwd: projectRoot,
91
- stdio: 'inherit',
92
- timeout: 120000, // 2 min timeout
93
- });
94
- }
95
- catch (err) {
96
- console.error(` Failed to install ${skill.name}`);
97
- }
98
- }
99
- }
100
- // Ensure .claude/skills symlink exists
101
- ensureSkillsSymlink(projectRoot);
102
- // Sync AGENTS.md
103
- if (sync) {
104
- await syncAgentsMd(projectRoot, commit);
105
- }
106
- console.log('Skills install complete');
107
- }
108
- // ============================================================================
109
- // Helper Functions
110
- // ============================================================================
111
- /**
112
- * Check if a command exists in PATH
113
- */
114
- function commandExists(cmd) {
115
- try {
116
- const result = (0, node_child_process_1.spawnSync)('which', [cmd], { encoding: 'utf8' });
117
- return result.status === 0;
118
- }
119
- catch {
120
- return false;
121
- }
122
- }
123
- /**
124
- * Check if a line contains a glob pattern.
125
- */
126
- function isGlobPattern(line) {
127
- return line.includes('*');
128
- }
129
- /**
130
- * Expand a glob pattern to find all subdirectories containing SKILL.md.
131
- */
132
- function expandGlobPattern(pattern, basePath) {
133
- const sources = [];
134
- const isRecursive = pattern.endsWith('/**');
135
- const basePattern = pattern.replace(/\/\*+$/, '');
136
- let searchRoot;
137
- if (basePattern.startsWith('./') || basePattern.startsWith('../')) {
138
- searchRoot = path.resolve(path.dirname(basePath), basePattern);
139
- }
140
- else if (basePattern.startsWith('/')) {
141
- searchRoot = basePattern;
142
- }
143
- else if (basePattern.startsWith('~')) {
144
- searchRoot = basePattern.replace(/^~/, process.env.HOME || '~');
145
- }
146
- else {
147
- searchRoot = path.resolve(path.dirname(basePath), basePattern);
148
- }
149
- if (!fs.existsSync(searchRoot)) {
150
- console.warn(`Warning: Glob pattern base directory not found: ${searchRoot}`);
151
- return sources;
152
- }
153
- const findSkillDirs = (dir, depth) => {
154
- try {
155
- const entries = fs.readdirSync(dir, { withFileTypes: true });
156
- for (const entry of entries) {
157
- if (!entry.isDirectory() || entry.name.startsWith('.')) {
158
- continue;
159
- }
160
- const fullPath = path.join(dir, entry.name);
161
- const skillMdPath = path.join(fullPath, 'SKILL.md');
162
- if (fs.existsSync(skillMdPath)) {
163
- const name = entry.name;
164
- const relativePath = path.relative(path.dirname(basePath), fullPath);
165
- const source = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
166
- sources.push({
167
- raw: pattern,
168
- source: source,
169
- type: 'local',
170
- name: name,
171
- });
172
- }
173
- if (isRecursive || depth === 0) {
174
- findSkillDirs(fullPath, depth + 1);
175
- }
176
- }
177
- }
178
- catch (err) {
179
- console.warn(`Warning: Error reading directory ${dir}:`, err);
180
- }
181
- };
182
- findSkillDirs(searchRoot, 0);
183
- return sources;
184
- }
185
- /**
186
- * Parse skills.txt and return skill sources.
187
- */
188
- function parseSkillsManifest(manifestPath) {
189
- if (!fs.existsSync(manifestPath)) {
190
- return [];
191
- }
192
- const content = fs.readFileSync(manifestPath, 'utf-8');
193
- const sources = [];
194
- for (const rawLine of content.split('\n')) {
195
- const line = rawLine.split('#')[0].trim();
196
- if (!line)
197
- continue;
198
- if (isGlobPattern(line)) {
199
- const expanded = expandGlobPattern(line, manifestPath);
200
- sources.push(...expanded);
201
- }
202
- else {
203
- sources.push(parseSkillSource(line));
204
- }
205
- }
206
- return sources;
207
- }
208
- /**
209
- * Parse a single skill source line.
210
- */
211
- function parseSkillSource(line) {
212
- // URL
213
- if (line.startsWith('https://') || line.startsWith('http://')) {
214
- const name = extractNameFromUrl(line);
215
- return { raw: line, source: line, type: 'url', name };
216
- }
217
- // Explicit GitHub prefix
218
- if (line.startsWith('github:')) {
219
- const repo = line.slice(7);
220
- const name = extractNameFromRepo(repo);
221
- return { raw: line, source: line, type: 'github', name };
222
- }
223
- // Local absolute path
224
- if (line.startsWith('/') || line.startsWith('~')) {
225
- const name = path.basename(line);
226
- return { raw: line, source: line, type: 'local', name };
227
- }
228
- // Local relative path
229
- if (line.startsWith('./') || line.startsWith('../')) {
230
- const name = path.basename(line);
231
- return { raw: line, source: line, type: 'local', name };
232
- }
233
- // Check if it's a local path that exists
234
- if (fs.existsSync(line)) {
235
- const name = path.basename(line);
236
- return { raw: line, source: `./${line}`, type: 'local', name };
237
- }
238
- // Assume GitHub shorthand
239
- if (line.includes('/') && !line.includes(' ')) {
240
- const name = extractNameFromRepo(line);
241
- return { raw: line, source: line, type: 'github', name };
242
- }
243
- // Fallback: treat as local path
244
- const name = path.basename(line);
245
- return { raw: line, source: `./${line}`, type: 'local', name };
246
- }
247
- function extractNameFromUrl(url) {
248
- const match = url.match(/github\.com\/[^/]+\/([^/]+)/);
249
- if (match) {
250
- return match[1].replace(/\.git$/, '');
251
- }
252
- return path.basename(new URL(url).pathname).replace(/\.git$/, '');
253
- }
254
- function extractNameFromRepo(repo) {
255
- const parts = repo.split('/');
256
- return parts[parts.length - 1].replace(/\.git$/, '');
257
- }
258
- /**
259
- * Get list of installed skill names from .agent/skills directory.
260
- */
261
- function getInstalledSkills(skillsDir) {
262
- const installed = new Set();
263
- if (!fs.existsSync(skillsDir)) {
264
- return installed;
265
- }
266
- const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
267
- for (const entry of entries) {
268
- if (entry.isDirectory() && !entry.name.startsWith('.')) {
269
- installed.add(entry.name);
270
- }
271
- }
272
- return installed;
273
- }
274
- /**
275
- * Ensure .claude/skills symlink points to .agent/skills
276
- */
277
- function ensureSkillsSymlink(projectRoot) {
278
- const agentSkills = path.join(projectRoot, '.agent', 'skills');
279
- const claudeDir = path.join(projectRoot, '.claude');
280
- const claudeSkills = path.join(claudeDir, 'skills');
281
- // Ensure directories exist
282
- if (!fs.existsSync(agentSkills)) {
283
- fs.mkdirSync(agentSkills, { recursive: true });
284
- }
285
- if (!fs.existsSync(claudeDir)) {
286
- fs.mkdirSync(claudeDir, { recursive: true });
287
- }
288
- // Check existing symlink
289
- if (fs.existsSync(claudeSkills)) {
290
- const stat = fs.lstatSync(claudeSkills);
291
- if (stat.isSymbolicLink()) {
292
- const target = fs.readlinkSync(claudeSkills);
293
- if (target === '../.agent/skills' || target === agentSkills) {
294
- return; // Already correct
295
- }
296
- fs.unlinkSync(claudeSkills);
297
- }
298
- else {
299
- console.log('.claude/skills exists and is not a symlink; skipping');
300
- return;
301
- }
302
- }
303
- try {
304
- fs.symlinkSync('../.agent/skills', claudeSkills);
305
- console.log('Linked .claude/skills -> .agent/skills');
306
- }
307
- catch (err) {
308
- console.warn('Warning: Failed to create symlink:', err);
309
- }
310
- }
311
- /**
312
- * Sync AGENTS.md with installed skills and optionally commit.
313
- */
314
- async function syncAgentsMd(projectRoot, commit) {
315
- const agentsMdPath = path.join(projectRoot, 'AGENTS.md');
316
- // Get hash before sync
317
- const hashBefore = getFileHash(agentsMdPath);
318
- console.log('Syncing AGENTS.md with installed skills...');
319
- try {
320
- (0, node_child_process_1.execSync)('openskills sync --yes', {
321
- cwd: projectRoot,
322
- stdio: 'inherit',
323
- timeout: 30000,
324
- });
325
- }
326
- catch (err) {
327
- console.warn('Warning: openskills sync failed');
328
- return;
329
- }
330
- const hashAfter = getFileHash(agentsMdPath);
331
- if (hashBefore === hashAfter) {
332
- console.log('AGENTS.md unchanged');
333
- return;
334
- }
335
- console.log('AGENTS.md updated');
336
- if (!commit) {
337
- return;
338
- }
339
- // Check if we're in a git repo
340
- if (!isGitRepo(projectRoot)) {
341
- console.log('Not a git repo; skipping commit');
342
- return;
343
- }
344
- console.log('Committing AGENTS.md changes...');
345
- try {
346
- (0, node_child_process_1.execSync)(`git add ${JSON.stringify(agentsMdPath)}`, { cwd: projectRoot });
347
- (0, node_child_process_1.execSync)('git commit -m "chore: sync AGENTS.md with installed skills"', {
348
- cwd: projectRoot,
349
- stdio: 'inherit',
350
- });
351
- console.log('Committed AGENTS.md changes');
352
- }
353
- catch (err) {
354
- console.warn('Warning: failed to commit AGENTS.md');
355
- }
356
- }
357
- /**
358
- * Get file hash using git or fallback to content hash.
359
- */
360
- function getFileHash(filePath) {
361
- if (!fs.existsSync(filePath)) {
362
- return null;
363
- }
364
- try {
365
- const result = (0, node_child_process_1.execSync)(`git hash-object ${JSON.stringify(filePath)}`, {
366
- encoding: 'utf8',
367
- });
368
- return result.trim();
369
- }
370
- catch {
371
- // Fallback to simple content hash
372
- const content = fs.readFileSync(filePath, 'utf8');
373
- return Buffer.from(content).toString('base64').slice(0, 40);
374
- }
375
- }
376
- /**
377
- * Check if current directory is a git repo.
378
- */
379
- function isGitRepo(dir) {
380
- try {
381
- (0, node_child_process_1.execSync)('git rev-parse --git-dir', {
382
- cwd: dir,
383
- stdio: ['pipe', 'pipe', 'pipe'],
384
- });
385
- return true;
386
- }
387
- catch {
388
- return false;
389
- }
390
- }