@maccesar/aiskills 1.7.0
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/README.md +531 -0
- package/bin/aiskills.js +76 -0
- package/lib/cache.js +49 -0
- package/lib/cleanup.js +77 -0
- package/lib/commands/auto-update.js +131 -0
- package/lib/commands/doctor.js +139 -0
- package/lib/commands/list.js +77 -0
- package/lib/commands/skills.js +263 -0
- package/lib/commands/status.js +94 -0
- package/lib/commands/uninstall.js +182 -0
- package/lib/commands/update.js +149 -0
- package/lib/config.js +90 -0
- package/lib/downloader.js +110 -0
- package/lib/hooks.js +74 -0
- package/lib/installer.js +114 -0
- package/lib/platform.js +112 -0
- package/lib/prompts/checkboxCancel.js +264 -0
- package/lib/prompts/selectCancel.js +204 -0
- package/lib/symlink.js +154 -0
- package/lib/utils.js +49 -0
- package/package.json +61 -0
- package/skills/humaniza/SKILL.md +51 -0
- package/skills/humaniza/agents/openai.yaml +4 -0
- package/skills/humaniza/references/ai-patterns-es.md +51 -0
- package/skills/humaniza/references/checklist.md +9 -0
- package/skills/humaniza/references/examples.md +17 -0
- package/skills/humaniza/references/lexicon-es-mx.md +36 -0
- package/skills/humaniza/references/modes-es-mx.md +41 -0
- package/skills/humaniza/references/voice-es-mx.md +24 -0
- package/skills/refactoring-ui/SKILL.md +59 -0
- package/skills/refactoring-ui/references/01-design-process.md +72 -0
- package/skills/refactoring-ui/references/02-visual-hierarchy.md +84 -0
- package/skills/refactoring-ui/references/03-layout-spacing.md +69 -0
- package/skills/refactoring-ui/references/04-typography.md +70 -0
- package/skills/refactoring-ui/references/05-color.md +96 -0
- package/skills/refactoring-ui/references/06-depth-shadows.md +74 -0
- package/skills/refactoring-ui/references/07-images.md +75 -0
- package/skills/refactoring-ui/references/08-finishing-touches.md +91 -0
- package/skills/stitch-showcase/SKILL.md +411 -0
- package/skills/stitch-showcase/references/01-navbar.md +52 -0
- package/skills/stitch-showcase/references/02-hero.md +56 -0
- package/skills/stitch-showcase/references/03-design-system.md +102 -0
- package/skills/stitch-showcase/references/04-screen-gallery.md +102 -0
- package/skills/stitch-showcase/references/05-viewer-web.md +105 -0
- package/skills/stitch-showcase/references/06-viewer-mobile.md +104 -0
- package/skills/stitch-showcase/references/07-theme-system.md +77 -0
- package/skills/stitch-showcase/references/08-type-detection.md +81 -0
- package/skills/stitch-showcase/references/09-quality-standards.md +126 -0
- package/skills/stitch-showcase/references/10-component-standardization.md +40 -0
- package/skills/stitch-showcase/references/11-component-catalog.md +70 -0
- package/skills/stitch-showcase/references/catalog-template.html +841 -0
- package/skills/stitch-showcase/references/index.html +299 -0
- package/skills/stitch-showcase/references/viewer.html +412 -0
- package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/apply_canonical.py +238 -0
- package/skills/stitch-showcase/scripts/build_showcase.py +2103 -0
- package/skills/stitch-showcase/scripts/component_utils.py +398 -0
- package/skills/stitch-showcase/scripts/detect_components.py +284 -0
- package/skills/stitch-showcase/scripts/extract_catalog.py +913 -0
- package/skills/stitch-showcase/scripts/extract_text.py +268 -0
- package/skills/stitch-showcase/scripts/extract_zips.py +178 -0
- package/skills/stitch-showcase/scripts/parse_design_md.py +397 -0
- package/skills/vscode-extension-dev/SKILL.md +114 -0
- package/skills/vscode-extension-dev/references/api-patterns.md +625 -0
- package/skills/vscode-extension-dev/references/architecture.md +287 -0
- package/skills/vscode-extension-dev/references/package-json-schema.md +345 -0
- package/skills/vscode-extension-dev/references/publishing.md +251 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status command
|
|
3
|
+
* Shows a quick overview of what's installed (read-only)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { existsSync, lstatSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import {
|
|
11
|
+
SKILLS,
|
|
12
|
+
PACKAGE_VERSION,
|
|
13
|
+
getAgentsSkillsDir,
|
|
14
|
+
getConfigDir,
|
|
15
|
+
getPlatforms,
|
|
16
|
+
} from '../config.js';
|
|
17
|
+
import { hasHook } from '../hooks.js';
|
|
18
|
+
import { readLastCheck } from '../cache.js';
|
|
19
|
+
|
|
20
|
+
const CHECK = chalk.green('✓');
|
|
21
|
+
const CROSS = chalk.red('✗');
|
|
22
|
+
|
|
23
|
+
function countInstalledSkills(skillsDir) {
|
|
24
|
+
let count = 0;
|
|
25
|
+
for (const skill of SKILLS) {
|
|
26
|
+
if (existsSync(join(skillsDir, skill))) {
|
|
27
|
+
count++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return count;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function countLinkedSkills(platformSkillsDir) {
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const skill of SKILLS) {
|
|
36
|
+
const linkPath = join(platformSkillsDir, skill);
|
|
37
|
+
try {
|
|
38
|
+
lstatSync(linkPath);
|
|
39
|
+
count++;
|
|
40
|
+
} catch {
|
|
41
|
+
// not found
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return count;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatLastCheck(data) {
|
|
48
|
+
if (!data) return chalk.gray('never');
|
|
49
|
+
const date = new Date(data.lastCheck);
|
|
50
|
+
const formatted = date.toISOString().replace('T', ' ').slice(0, 16);
|
|
51
|
+
return `${formatted} (v${data.latestVersion})`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function statusCommand() {
|
|
55
|
+
const homeDir = os.homedir();
|
|
56
|
+
const skillsDir = getAgentsSkillsDir(homeDir);
|
|
57
|
+
const claudeDir = join(homeDir, '.claude');
|
|
58
|
+
const cacheDir = getConfigDir();
|
|
59
|
+
|
|
60
|
+
// Skills count
|
|
61
|
+
const installedCount = countInstalledSkills(skillsDir);
|
|
62
|
+
const totalCount = SKILLS.length;
|
|
63
|
+
|
|
64
|
+
// Hook check
|
|
65
|
+
const hookExists = hasHook(claudeDir);
|
|
66
|
+
|
|
67
|
+
// Cache
|
|
68
|
+
const lastCheck = readLastCheck(cacheDir);
|
|
69
|
+
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log(chalk.bold('AI Skills Status'));
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(` Version: v${PACKAGE_VERSION}`);
|
|
74
|
+
console.log(` Skills: ${installedCount}/${totalCount} installed`);
|
|
75
|
+
console.log(` Hook: Claude Code SessionStart ${hookExists ? CHECK : CROSS}`);
|
|
76
|
+
console.log(` Last check: ${formatLastCheck(lastCheck)}`);
|
|
77
|
+
|
|
78
|
+
// Platforms
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log(' Platforms:');
|
|
81
|
+
const platforms = getPlatforms(homeDir);
|
|
82
|
+
for (const platform of platforms) {
|
|
83
|
+
const linked = countLinkedSkills(platform.skillsDir);
|
|
84
|
+
if (linked > 0) {
|
|
85
|
+
console.log(` ${platform.displayName.padEnd(13)} ${CHECK} ${linked} skills linked`);
|
|
86
|
+
} else {
|
|
87
|
+
console.log(` ${platform.displayName.padEnd(13)} ${CROSS} ${chalk.gray('not linked')}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default { statusCommand };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uninstall command
|
|
3
|
+
* Removes installed skills and symlinks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import { SKILLS, getConfigDir } from '../config.js';
|
|
10
|
+
import {
|
|
11
|
+
detectPlatforms,
|
|
12
|
+
} from '../platform.js';
|
|
13
|
+
import {
|
|
14
|
+
removeSkillSymlinks,
|
|
15
|
+
removeSkills,
|
|
16
|
+
} from '../cleanup.js';
|
|
17
|
+
import { removeHook } from '../hooks.js';
|
|
18
|
+
import checkbox, { Separator } from '../prompts/checkboxCancel.js';
|
|
19
|
+
import { existsSync } from 'fs';
|
|
20
|
+
import { rm } from 'fs/promises';
|
|
21
|
+
import { join, resolve } from 'path';
|
|
22
|
+
import { getAgentsSkillsDir } from '../config.js';
|
|
23
|
+
import { getSkillList } from '../cleanup.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Uninstall command handler
|
|
27
|
+
*/
|
|
28
|
+
export async function uninstallCommand(options) {
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(chalk.bold.blue('AI Skills Uninstaller'));
|
|
31
|
+
console.log('');
|
|
32
|
+
|
|
33
|
+
const projectDir = resolve(process.cwd());
|
|
34
|
+
const baseDir = options.local ? projectDir : undefined;
|
|
35
|
+
if (options.local) {
|
|
36
|
+
console.log(chalk.cyan('Mode: Local uninstallation (current project)'));
|
|
37
|
+
console.log('');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const detectedPlatforms = detectPlatforms(baseDir);
|
|
41
|
+
|
|
42
|
+
const skillList = getSkillList();
|
|
43
|
+
const homeSkillsDir = getAgentsSkillsDir();
|
|
44
|
+
const projectSkillsDir = getAgentsSkillsDir(projectDir);
|
|
45
|
+
|
|
46
|
+
const hasAnyInDir = (dir, names) =>
|
|
47
|
+
!!dir && existsSync(dir) && names.some((name) => existsSync(join(dir, name)));
|
|
48
|
+
|
|
49
|
+
const hasHomeSkills = hasAnyInDir(homeSkillsDir, skillList);
|
|
50
|
+
const hasProjectSkills = options.local && hasAnyInDir(projectSkillsDir, skillList);
|
|
51
|
+
|
|
52
|
+
const hasHomeSymlinks = detectedPlatforms.some((platform) =>
|
|
53
|
+
hasAnyInDir(platform.skillsDir, skillList)
|
|
54
|
+
);
|
|
55
|
+
const hasProjectSymlinks = options.local && detectPlatforms(projectDir).some((platform) =>
|
|
56
|
+
hasAnyInDir(platform.skillsDir, skillList)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const choices = [];
|
|
60
|
+
if (hasHomeSkills) {
|
|
61
|
+
choices.push({ name: 'Skills from the `home` directory', value: 'skills-home', checked: false });
|
|
62
|
+
}
|
|
63
|
+
if (hasProjectSkills) {
|
|
64
|
+
choices.push({ name: 'Skills from the `project` directory', value: 'skills-project', checked: false });
|
|
65
|
+
}
|
|
66
|
+
if (hasHomeSymlinks) {
|
|
67
|
+
choices.push({ name: 'Skill symlinks from `home` directory', value: 'symlinks-home', checked: true });
|
|
68
|
+
}
|
|
69
|
+
if (hasProjectSymlinks) {
|
|
70
|
+
choices.push({ name: 'Skill symlinks from `project` directory', value: 'symlinks-project', checked: false });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (choices.length === 0) {
|
|
74
|
+
console.log(chalk.yellow('No skills or symlinks found.'));
|
|
75
|
+
console.log('');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let targets = [];
|
|
80
|
+
try {
|
|
81
|
+
targets = await checkbox({
|
|
82
|
+
message: 'What do you want to uninstall:',
|
|
83
|
+
choices: [
|
|
84
|
+
...choices,
|
|
85
|
+
new Separator(' '),
|
|
86
|
+
],
|
|
87
|
+
shortcuts: { invert: null },
|
|
88
|
+
theme: {
|
|
89
|
+
style: {
|
|
90
|
+
renderSelectedChoices: () => '',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.log('\nCancelled.');
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (targets.includes('cancel')) {
|
|
100
|
+
console.log('Cancelled.');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (targets.length === 0) {
|
|
105
|
+
console.log(chalk.yellow('Nothing to uninstall. Cancelled.'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const spinner = ora();
|
|
110
|
+
let actionTaken = false;
|
|
111
|
+
|
|
112
|
+
if (targets.includes('symlinks-home')) {
|
|
113
|
+
for (const platform of detectedPlatforms) {
|
|
114
|
+
spinner.start(`Removing ${platform.displayName} symlinks...`);
|
|
115
|
+
const symlinkResult = removeSkillSymlinks(platform.skillsDir);
|
|
116
|
+
if (symlinkResult.removed.length > 0) {
|
|
117
|
+
spinner.succeed(`${platform.displayName}: Skills unlinked`);
|
|
118
|
+
actionTaken = true;
|
|
119
|
+
} else {
|
|
120
|
+
spinner.info(`${platform.displayName}: No symlinks found`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (targets.includes('symlinks-project')) {
|
|
126
|
+
const projectPlatforms = detectPlatforms(projectDir);
|
|
127
|
+
for (const platform of projectPlatforms) {
|
|
128
|
+
spinner.start(`Removing ${platform.displayName} symlinks...`);
|
|
129
|
+
const symlinkResult = removeSkillSymlinks(platform.skillsDir);
|
|
130
|
+
if (symlinkResult.removed.length > 0) {
|
|
131
|
+
spinner.succeed(`${platform.displayName}: Skills unlinked`);
|
|
132
|
+
actionTaken = true;
|
|
133
|
+
} else {
|
|
134
|
+
spinner.info(`${platform.displayName}: No symlinks found`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (targets.includes('skills-home')) {
|
|
140
|
+
spinner.start('Removing skills...');
|
|
141
|
+
const skillsResult = removeSkills(undefined);
|
|
142
|
+
if (skillsResult.removed.length > 0) {
|
|
143
|
+
spinner.succeed(`${SKILLS.length} skills removed`);
|
|
144
|
+
actionTaken = true;
|
|
145
|
+
} else {
|
|
146
|
+
spinner.info('No skills to remove');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (targets.includes('skills-project')) {
|
|
151
|
+
spinner.start('Removing skills...');
|
|
152
|
+
const skillsResult = removeSkills(projectDir);
|
|
153
|
+
if (skillsResult.removed.length > 0) {
|
|
154
|
+
spinner.succeed(`${SKILLS.length} skills removed`);
|
|
155
|
+
actionTaken = true;
|
|
156
|
+
} else {
|
|
157
|
+
spinner.info('No skills to remove');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Remove Claude Code SessionStart hook
|
|
162
|
+
const claudeDir = join(os.homedir(), '.claude');
|
|
163
|
+
removeHook(claudeDir);
|
|
164
|
+
|
|
165
|
+
// Clean up cache directory
|
|
166
|
+
const cacheDir = getConfigDir();
|
|
167
|
+
if (existsSync(cacheDir)) {
|
|
168
|
+
await rm(cacheDir, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (actionTaken) {
|
|
172
|
+
console.log('');
|
|
173
|
+
console.log(chalk.green('✓ Uninstallation complete!'));
|
|
174
|
+
console.log('');
|
|
175
|
+
} else {
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log(chalk.yellow('No changes were necessary.'));
|
|
178
|
+
console.log('');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default uninstallCommand;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update command
|
|
3
|
+
* Checks for a newer CLI version, then syncs skills from the installed package
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import {
|
|
9
|
+
PACKAGE_VERSION,
|
|
10
|
+
REPO_URL,
|
|
11
|
+
SKILLS,
|
|
12
|
+
} from '../config.js';
|
|
13
|
+
import {
|
|
14
|
+
detectPlatforms,
|
|
15
|
+
} from '../platform.js';
|
|
16
|
+
import { cleanupLegacyArtifacts, getSkillList } from '../cleanup.js';
|
|
17
|
+
import {
|
|
18
|
+
installSkills,
|
|
19
|
+
getLocalRepoDir,
|
|
20
|
+
} from '../installer.js';
|
|
21
|
+
import {
|
|
22
|
+
checkForUpdate,
|
|
23
|
+
fetchLatestNpmVersion,
|
|
24
|
+
} from '../downloader.js';
|
|
25
|
+
import { createSkillSymlinks } from '../symlink.js';
|
|
26
|
+
import { getAgentsSkillsDir } from '../config.js';
|
|
27
|
+
import { existsSync } from 'fs';
|
|
28
|
+
import { join } from 'path';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if a platform has any skill symlinks installed
|
|
32
|
+
*/
|
|
33
|
+
function hasAnySkillSymlink(platformSkillsDir) {
|
|
34
|
+
if (!platformSkillsDir || !existsSync(platformSkillsDir)) return false;
|
|
35
|
+
const skillList = getSkillList();
|
|
36
|
+
return skillList.some((skill) => existsSync(join(platformSkillsDir, skill)));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Perform the actual update for a specific scope
|
|
41
|
+
*/
|
|
42
|
+
async function performUpdate(baseDir, repoDir, spinner) {
|
|
43
|
+
const detectedPlatforms = detectPlatforms(baseDir);
|
|
44
|
+
const platformsWithSymlinks = detectedPlatforms.filter((p) =>
|
|
45
|
+
hasAnySkillSymlink(p.skillsDir)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
spinner.start('Syncing skills...');
|
|
49
|
+
const skillsResult = await installSkills(repoDir, baseDir);
|
|
50
|
+
spinner.succeed(`${skillsResult.installed.length} skills updated`);
|
|
51
|
+
|
|
52
|
+
cleanupLegacyArtifacts(baseDir);
|
|
53
|
+
|
|
54
|
+
for (const platform of platformsWithSymlinks) {
|
|
55
|
+
await createSkillSymlinks(
|
|
56
|
+
platform.skillsDir,
|
|
57
|
+
SKILLS,
|
|
58
|
+
baseDir
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Update command handler
|
|
65
|
+
*/
|
|
66
|
+
export async function updateCommand(options) {
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(chalk.bold.blue('AI Skills Updater'));
|
|
69
|
+
console.log('');
|
|
70
|
+
|
|
71
|
+
const spinner = ora();
|
|
72
|
+
|
|
73
|
+
const baseDir = options.local ? process.cwd() : undefined;
|
|
74
|
+
|
|
75
|
+
if (baseDir) {
|
|
76
|
+
console.log(chalk.cyan('Mode: Local update (current project)'));
|
|
77
|
+
} else {
|
|
78
|
+
console.log(chalk.cyan('Mode: Global update (user home)'));
|
|
79
|
+
}
|
|
80
|
+
console.log('');
|
|
81
|
+
|
|
82
|
+
// Verify skills are installed
|
|
83
|
+
const skillsDir = getAgentsSkillsDir(baseDir);
|
|
84
|
+
const hasSkillsInstalled = skillsDir && SKILLS.some((skill) => existsSync(join(skillsDir, skill)));
|
|
85
|
+
if (!hasSkillsInstalled) {
|
|
86
|
+
console.log(chalk.yellow('No skills installed at this location.'));
|
|
87
|
+
console.log('Install them first with:');
|
|
88
|
+
console.log(' aiskills install');
|
|
89
|
+
console.log('');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check for updates
|
|
94
|
+
spinner.start('Checking for updates...');
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const hasUpdate = await checkForUpdate(PACKAGE_VERSION);
|
|
98
|
+
|
|
99
|
+
if (hasUpdate) {
|
|
100
|
+
let latestVersion = '(newer)';
|
|
101
|
+
try {
|
|
102
|
+
latestVersion = await fetchLatestNpmVersion();
|
|
103
|
+
} catch {
|
|
104
|
+
// Ignore
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
spinner.warn('New version available');
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.yellow('A newer version of aiskills is available on npm:'));
|
|
110
|
+
console.log(` Current: ${chalk.gray('v' + PACKAGE_VERSION)}`);
|
|
111
|
+
console.log(` Latest: ${chalk.green(latestVersion)}`);
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log('Update the CLI with:');
|
|
114
|
+
console.log(` ${chalk.cyan('npm update -g @maccesar/aiskills')}`);
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log('After updating, run this command again:');
|
|
117
|
+
console.log(` ${chalk.cyan('aiskills update')}`);
|
|
118
|
+
console.log('');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
spinner.succeed(`CLI is up to date (v${PACKAGE_VERSION})`);
|
|
123
|
+
|
|
124
|
+
const repoDir = getLocalRepoDir();
|
|
125
|
+
if (!repoDir) {
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log(chalk.red('Error: Could not locate skills source directory.'));
|
|
128
|
+
console.log('Try reinstalling with:');
|
|
129
|
+
console.log(` ${chalk.cyan('npm install -g @maccesar/aiskills')}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await performUpdate(baseDir, repoDir, spinner);
|
|
134
|
+
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log(chalk.green('✓ Update complete!'));
|
|
137
|
+
console.log('');
|
|
138
|
+
|
|
139
|
+
} catch (error) {
|
|
140
|
+
spinner.fail('Update failed');
|
|
141
|
+
console.error(chalk.red(error.message));
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log('You can try manually installing from:');
|
|
144
|
+
console.log(chalk.cyan(REPO_URL));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default updateCommand;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants for AI Skills
|
|
3
|
+
* Single source of truth for version management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
|
|
10
|
+
// Read package.json version dynamically
|
|
11
|
+
let packageVersion = '1.0.0';
|
|
12
|
+
try {
|
|
13
|
+
const packagePath = new URL('../package.json', import.meta.url);
|
|
14
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
15
|
+
packageVersion = pkg.version;
|
|
16
|
+
} catch {
|
|
17
|
+
// Use default version if package.json not found
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Version management
|
|
21
|
+
export const PACKAGE_VERSION = packageVersion;
|
|
22
|
+
|
|
23
|
+
// Repository configuration
|
|
24
|
+
export const REPO_URL = 'https://github.com/macCesar/aiskills';
|
|
25
|
+
export const REPO_RAW_URL = 'https://raw.githubusercontent.com/macCesar/aiskills/main';
|
|
26
|
+
export const REPO_API_URL = 'https://api.github.com/repos/macCesar/aiskills';
|
|
27
|
+
|
|
28
|
+
// Skills to install
|
|
29
|
+
export const SKILLS = [
|
|
30
|
+
'humaniza',
|
|
31
|
+
'refactoring-ui',
|
|
32
|
+
'stitch-showcase',
|
|
33
|
+
'vscode-extension-dev',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// Legacy skills to remove during updates/uninstall
|
|
37
|
+
export const LEGACY_SKILLS = [];
|
|
38
|
+
|
|
39
|
+
// Cache/config directory
|
|
40
|
+
export const getConfigDir = () => path.join(os.homedir(), '.aiskills');
|
|
41
|
+
|
|
42
|
+
// Directory paths
|
|
43
|
+
export const getAgentsSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.agents', 'skills');
|
|
44
|
+
export const getClaudeSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.claude', 'skills');
|
|
45
|
+
export const getGeminiSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.gemini', 'skills');
|
|
46
|
+
export const getCodexSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.codex', 'skills');
|
|
47
|
+
|
|
48
|
+
// AI platform detection
|
|
49
|
+
export const getPlatforms = (baseDir = os.homedir()) => [
|
|
50
|
+
{
|
|
51
|
+
name: 'claude',
|
|
52
|
+
displayName: 'Claude Code',
|
|
53
|
+
skillsDir: getClaudeSkillsDir(baseDir),
|
|
54
|
+
configDir: path.join(baseDir, '.claude'),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'gemini',
|
|
58
|
+
displayName: 'Gemini CLI',
|
|
59
|
+
skillsDir: getGeminiSkillsDir(baseDir),
|
|
60
|
+
configDir: path.join(baseDir, '.gemini'),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'codex',
|
|
64
|
+
displayName: 'Codex CLI',
|
|
65
|
+
skillsDir: getCodexSkillsDir(baseDir),
|
|
66
|
+
configDir: path.join(baseDir, '.codex'),
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// API configuration
|
|
71
|
+
export const GITHUB_API_HEADERS = {
|
|
72
|
+
Accept: 'application/vnd.github.v3+json',
|
|
73
|
+
'User-Agent': 'aiskills',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default {
|
|
77
|
+
PACKAGE_VERSION,
|
|
78
|
+
REPO_URL,
|
|
79
|
+
REPO_RAW_URL,
|
|
80
|
+
REPO_API_URL,
|
|
81
|
+
SKILLS,
|
|
82
|
+
LEGACY_SKILLS,
|
|
83
|
+
getConfigDir,
|
|
84
|
+
getAgentsSkillsDir,
|
|
85
|
+
getClaudeSkillsDir,
|
|
86
|
+
getGeminiSkillsDir,
|
|
87
|
+
getCodexSkillsDir,
|
|
88
|
+
getPlatforms,
|
|
89
|
+
GITHUB_API_HEADERS,
|
|
90
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub download utilities
|
|
3
|
+
* Fetches releases and archives from GitHub
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createWriteStream, existsSync, mkdirSync } from 'fs';
|
|
7
|
+
import { tmpdir } from 'os';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { pipeline } from 'stream/promises';
|
|
10
|
+
import { unlink } from 'fs/promises';
|
|
11
|
+
import { extract } from 'tar';
|
|
12
|
+
import { REPO_API_URL, REPO_RAW_URL, GITHUB_API_HEADERS } from './config.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Download a file from URL to local path
|
|
16
|
+
* @param {string} url - URL to download
|
|
17
|
+
* @param {string} destPath - Destination path
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
export async function downloadFile(url, destPath) {
|
|
21
|
+
const response = await fetch(url);
|
|
22
|
+
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`Failed to download: ${response.statusText}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dir = dirname(destPath);
|
|
28
|
+
if (!existsSync(dir)) {
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await pipeline(response.body, createWriteStream(destPath));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Download and extract GitHub repository archive
|
|
37
|
+
* @param {string} destDir - Destination directory
|
|
38
|
+
* @param {string} ref - Git ref (branch, tag, commit)
|
|
39
|
+
* @returns {Promise<string>} Path to extracted directory
|
|
40
|
+
*/
|
|
41
|
+
export async function downloadRepoArchive(destDir, ref = 'main') {
|
|
42
|
+
const archiveUrl = `${REPO_API_URL}/tarball/${ref}`;
|
|
43
|
+
const tempFile = join(tmpdir(), `aiskills-${Date.now()}.tar.gz`);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await downloadFile(archiveUrl, tempFile);
|
|
47
|
+
|
|
48
|
+
await extract({
|
|
49
|
+
file: tempFile,
|
|
50
|
+
cwd: destDir,
|
|
51
|
+
strip: 1,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return destDir;
|
|
55
|
+
} finally {
|
|
56
|
+
if (existsSync(tempFile)) {
|
|
57
|
+
await unlink(tempFile);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fetch latest version from npm registry
|
|
64
|
+
* @returns {Promise<string>} Latest version number
|
|
65
|
+
*/
|
|
66
|
+
export async function fetchLatestNpmVersion() {
|
|
67
|
+
const response = await fetch('https://registry.npmjs.org/@maccesar/aiskills');
|
|
68
|
+
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`Failed to fetch npm info: ${response.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
return data['dist-tags'].latest;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if an update is available (checks npm)
|
|
79
|
+
* @param {string} currentVersion - Current version
|
|
80
|
+
* @returns {Promise<boolean>} True if update available
|
|
81
|
+
*/
|
|
82
|
+
export async function checkForUpdate(currentVersion) {
|
|
83
|
+
try {
|
|
84
|
+
const latestVersion = await fetchLatestNpmVersion();
|
|
85
|
+
const latest = latestVersion.replace(/^v/, '');
|
|
86
|
+
const current = currentVersion.replace(/^v/, '');
|
|
87
|
+
|
|
88
|
+
const latestParts = latest.split('.').map((v) => parseInt(v, 10));
|
|
89
|
+
const currentParts = current.split('.').map((v) => parseInt(v, 10));
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
|
|
92
|
+
const l = latestParts[i] || 0;
|
|
93
|
+
const c = currentParts[i] || 0;
|
|
94
|
+
|
|
95
|
+
if (l > c) return true;
|
|
96
|
+
if (l < c) return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default {
|
|
106
|
+
downloadFile,
|
|
107
|
+
downloadRepoArchive,
|
|
108
|
+
fetchLatestNpmVersion,
|
|
109
|
+
checkForUpdate,
|
|
110
|
+
};
|
package/lib/hooks.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code hook management
|
|
3
|
+
* Installs/removes the SessionStart hook for auto-update
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
const HOOK_COMMAND = 'aiskills auto-update --silent';
|
|
10
|
+
const SETTINGS_FILE = 'settings.json';
|
|
11
|
+
|
|
12
|
+
function readSettings(claudeDir) {
|
|
13
|
+
const settingsPath = join(claudeDir, SETTINGS_FILE);
|
|
14
|
+
if (!existsSync(settingsPath)) return {};
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeSettings(claudeDir, settings) {
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(claudeDir, SETTINGS_FILE),
|
|
25
|
+
JSON.stringify(settings, null, 2) + '\n',
|
|
26
|
+
'utf8'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Find the hook entry that contains our command
|
|
32
|
+
* Claude Code hook format: { hooks: [{ type: "command", command: "..." }] }
|
|
33
|
+
*/
|
|
34
|
+
function findHookEntry(sessionStartHooks) {
|
|
35
|
+
if (!Array.isArray(sessionStartHooks)) return -1;
|
|
36
|
+
return sessionStartHooks.findIndex((entry) =>
|
|
37
|
+
Array.isArray(entry.hooks) &&
|
|
38
|
+
entry.hooks.some((h) => h.type === 'command' && h.command === HOOK_COMMAND)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function hasHook(claudeDir) {
|
|
43
|
+
const settings = readSettings(claudeDir);
|
|
44
|
+
const hooks = settings.hooks?.SessionStart;
|
|
45
|
+
return findHookEntry(hooks) !== -1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function installHook(claudeDir) {
|
|
49
|
+
if (hasHook(claudeDir)) return;
|
|
50
|
+
const settings = readSettings(claudeDir);
|
|
51
|
+
if (!settings.hooks) settings.hooks = {};
|
|
52
|
+
if (!Array.isArray(settings.hooks.SessionStart)) settings.hooks.SessionStart = [];
|
|
53
|
+
settings.hooks.SessionStart.push({
|
|
54
|
+
hooks: [
|
|
55
|
+
{
|
|
56
|
+
type: 'command',
|
|
57
|
+
command: HOOK_COMMAND,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
writeSettings(claudeDir, settings);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function removeHook(claudeDir) {
|
|
65
|
+
if (!hasHook(claudeDir)) return;
|
|
66
|
+
const settings = readSettings(claudeDir);
|
|
67
|
+
const idx = findHookEntry(settings.hooks.SessionStart);
|
|
68
|
+
if (idx !== -1) {
|
|
69
|
+
settings.hooks.SessionStart.splice(idx, 1);
|
|
70
|
+
}
|
|
71
|
+
writeSettings(claudeDir, settings);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default { installHook, removeHook, hasHook };
|