@maccesar/aiskills 1.7.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -6
- package/lib/cleanup.js +19 -1
- package/lib/commands/list.js +76 -51
- package/lib/commands/skills.js +25 -0
- package/lib/commands/status.js +1 -1
- package/lib/commands/uninstall.js +37 -3
- package/lib/commands/update.js +94 -14
- package/lib/config.js +12 -0
- package/lib/downloader.js +59 -0
- package/lib/installer.js +69 -2
- package/package.json +1 -1
- package/skills/refactoring-ui/SKILL.md +1 -5
- package/skills/stitch-showcase/SKILL.md +1 -10
- package/skills/vscode-extension-dev/SKILL.md +1 -5
package/README.md
CHANGED
|
@@ -8,13 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
`aiskills` is a
|
|
11
|
+
`aiskills` is a toolkit of curated skills for AI coding assistants. It provides skill files for Claude Code, Gemini CLI, or Codex CLI.
|
|
12
12
|
|
|
13
13
|
Each skill is a small knowledge package: a `SKILL.md` file with YAML frontmatter plus a set of reference files. When a prompt matches the skill, the assistant reads those files and answers from the source material.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Option A: Plugin Marketplace (Claude Code only)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
/plugin marketplace add maccesar/aiskills
|
|
23
|
+
/plugin install aiskills@maccesar-aiskills
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Option B: CLI (Claude Code, Gemini CLI, Codex CLI)
|
|
18
27
|
|
|
19
28
|
```bash
|
|
20
29
|
# 1) Install the CLI
|
|
@@ -33,10 +42,14 @@ Installed files:
|
|
|
33
42
|
- All skills to `~/.agents/skills/`
|
|
34
43
|
- Platform symlinks in `~/.claude/skills/`, `~/.gemini/skills/`, or `~/.codex/skills/`
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
### Which option should I use?
|
|
46
|
+
|
|
47
|
+
| | Plugin (Option A) | CLI (Option B) |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| **Claude Code** | Recommended | Supported |
|
|
50
|
+
| **Gemini CLI** | Not available | Supported |
|
|
51
|
+
| **Codex CLI** | Not available | Supported |
|
|
52
|
+
| **Auto-updates** | Via marketplace | `aiskills update` |
|
|
40
53
|
|
|
41
54
|
---
|
|
42
55
|
|
|
@@ -65,6 +78,58 @@ Use `aiskills list` to see available skills from the command line. Pull requests
|
|
|
65
78
|
|
|
66
79
|
---
|
|
67
80
|
|
|
81
|
+
## Available commands
|
|
82
|
+
|
|
83
|
+
Slash commands are Claude Code-only. They ship with the plugin (Option A). The CLI distribution (Option B) installs skills only.
|
|
84
|
+
|
|
85
|
+
| Command | Purpose |
|
|
86
|
+
| ---------- | ------------------------------------------------------------------------------ |
|
|
87
|
+
| `/release` | Full release workflow: detect project, bump semver, update CHANGELOG + README, commit, push, tag, GitHub release |
|
|
88
|
+
|
|
89
|
+
### /release
|
|
90
|
+
|
|
91
|
+
End-to-end release janitor that works across project types: npm, Titanium (`tiapp.xml`), Composer, Cargo, CocoaPods, or versionless (git-tag-only) repos. **Designed for a dirty working tree** — it groups your uncommitted work into semantic commits, then ships the release on top.
|
|
92
|
+
|
|
93
|
+
When to use it:
|
|
94
|
+
- You have weeks of work in the working tree (with maybe a few interim commits you made along the way) and want one command to clean everything into proper semantic commits and ship a release.
|
|
95
|
+
- You maintain `CHANGELOG.md` in Keep-a-Changelog format and want the `[Unreleased]` section promoted automatically.
|
|
96
|
+
- You want the bump level inferred from Conventional Commits across both your existing commits and the proposed new ones, with the option to override.
|
|
97
|
+
|
|
98
|
+
Example prompts:
|
|
99
|
+
```
|
|
100
|
+
/release
|
|
101
|
+
/release minor
|
|
102
|
+
/release major
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
How it works:
|
|
106
|
+
1. **Detect** — reads git status, last tag, existing commits since the tag, version file, `CHANGELOG.md`, `README.md`, and `gh` availability.
|
|
107
|
+
2. **Group the working tree** — reads each modified/untracked file's diff, infers intent, and groups files into N proposed semantic commits (`feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `build`, `ci`). Excludes screenshots in repo root, scratch files, suspicious binaries — and lists them so you can override.
|
|
108
|
+
3. **Infer bump** — across the union of (existing commits since tag) + (proposed semantic commits): `BREAKING CHANGE` / `!:` → major, any `feat:` → minor, otherwise patch. An argument overrides.
|
|
109
|
+
4. **Compose CHANGELOG** — promotes `[Unreleased]` if present, or generates a Keep-a-Changelog entry from the union of all commits being shipped.
|
|
110
|
+
5. **Show one compact plan and stop** — header line, optional warnings, the N proposed commits with their files, the CHANGELOG entry, the release commit summary, the push/tag/release lines. If the current branch is not main/master, also offers to fast-forward merge or open a PR. **Waits for explicit confirmation.** You can ask it to merge, split, or skip any of the N commits before confirming.
|
|
111
|
+
6. **Execute** — lands each semantic commit (one at a time, with explicit `git add <files>` per commit, never `git add -A`), then the release commit (bump + CHANGELOG + README), pushes the branch, tags, and creates the GitHub release via `gh`. Optionally fast-forward merges to main or opens a PR if you confirmed that mode.
|
|
112
|
+
|
|
113
|
+
Confirmations:
|
|
114
|
+
- `proceed` / `sí` / `commitea` → release on current branch only.
|
|
115
|
+
- `merge` → release + fast-forward merge to main + push main, and leaves you on `main`. Aborts cleanly if main has diverged.
|
|
116
|
+
- `PR` → release + open pull request to main via `gh`.
|
|
117
|
+
|
|
118
|
+
Language policy (two independent axes):
|
|
119
|
+
- **Axis 1 — Interaction with you** — always in your language. The command detects the language from your messages and locks it before printing anything; if you switch, it switches with you.
|
|
120
|
+
- **Axis 2 — Project artifacts** (CHANGELOG entry, README edits, commit message, tag annotation, GitHub release title **and** body) — all share **one** language, detected from `README.md`. A Spanish README means everything in Spanish (typical for private / local projects); an English README means everything in English (typical for open source). No mixed-language releases. Tie-breaks fall back to `CHANGELOG.md` then `git log`; you can override before confirming.
|
|
121
|
+
|
|
122
|
+
Hard restrictions:
|
|
123
|
+
- Never `--force-push`, `--amend` published commits, or `--no-verify`.
|
|
124
|
+
- Aborts on merge conflicts or rebase-in-progress.
|
|
125
|
+
- Asks before creating the **first** tag on `main` / `master`.
|
|
126
|
+
- Skips push / tag / release gracefully when the repo has no remote or `gh` is not installed.
|
|
127
|
+
|
|
128
|
+
Distribution note:
|
|
129
|
+
- Available via the plugin install (Option A above). Slash commands are not distributed by the npm CLI (Option B) because they are a Claude Code feature.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
68
133
|
## How skills work
|
|
69
134
|
|
|
70
135
|
Skills activate based on what you ask. You can write prompts normally:
|
package/lib/cleanup.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cleanup helpers for skills and symlinks
|
|
2
|
+
* Cleanup helpers for skills, commands, and symlinks
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
SKILLS,
|
|
7
7
|
LEGACY_SKILLS,
|
|
8
|
+
COMMANDS,
|
|
9
|
+
LEGACY_COMMANDS,
|
|
8
10
|
getAgentsSkillsDir,
|
|
11
|
+
getClaudeCommandsDir,
|
|
9
12
|
} from './config.js';
|
|
10
13
|
import { detectPlatforms } from './platform.js';
|
|
11
14
|
import { existsSync, lstatSync, rmSync } from 'fs';
|
|
@@ -16,6 +19,11 @@ export function getSkillList({ includeLegacy = true, legacyOnly = false } = {})
|
|
|
16
19
|
return includeLegacy ? [...SKILLS, ...LEGACY_SKILLS] : [...SKILLS];
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
export function getCommandList({ includeLegacy = true, legacyOnly = false } = {}) {
|
|
23
|
+
if (legacyOnly) return [...LEGACY_COMMANDS];
|
|
24
|
+
return includeLegacy ? [...COMMANDS, ...LEGACY_COMMANDS] : [...COMMANDS];
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
function removeEntriesAtDir(dir, names, { suffix = '', recursive = true } = {}) {
|
|
20
28
|
const results = { removed: [], failed: [] };
|
|
21
29
|
if (!dir || !existsSync(dir)) return results;
|
|
@@ -51,8 +59,15 @@ export function removeSkills(baseDir, options = {}) {
|
|
|
51
59
|
return removeEntriesAtDir(skillsDir, skillList, { recursive: true });
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
export function removeCommands(baseDir, options = {}) {
|
|
63
|
+
const commandsDir = getClaudeCommandsDir(baseDir);
|
|
64
|
+
const commandList = getCommandList(options);
|
|
65
|
+
return removeEntriesAtDir(commandsDir, commandList, { suffix: '.md', recursive: false });
|
|
66
|
+
}
|
|
67
|
+
|
|
54
68
|
export function cleanupLegacyArtifacts(baseDir) {
|
|
55
69
|
removeSkills(baseDir, { legacyOnly: true });
|
|
70
|
+
removeCommands(baseDir, { legacyOnly: true });
|
|
56
71
|
|
|
57
72
|
const platforms = detectPlatforms(baseDir);
|
|
58
73
|
for (const platform of platforms) {
|
|
@@ -61,6 +76,7 @@ export function cleanupLegacyArtifacts(baseDir) {
|
|
|
61
76
|
|
|
62
77
|
if (baseDir) {
|
|
63
78
|
removeSkills(undefined, { legacyOnly: true });
|
|
79
|
+
removeCommands(undefined, { legacyOnly: true });
|
|
64
80
|
const globalPlatforms = detectPlatforms();
|
|
65
81
|
for (const platform of globalPlatforms) {
|
|
66
82
|
removeLegacySkillSymlinks(platform.skillsDir);
|
|
@@ -70,8 +86,10 @@ export function cleanupLegacyArtifacts(baseDir) {
|
|
|
70
86
|
|
|
71
87
|
export default {
|
|
72
88
|
getSkillList,
|
|
89
|
+
getCommandList,
|
|
73
90
|
removeSkillSymlinks,
|
|
74
91
|
removeLegacySkillSymlinks,
|
|
75
92
|
removeSkills,
|
|
93
|
+
removeCommands,
|
|
76
94
|
cleanupLegacyArtifacts,
|
|
77
95
|
};
|
package/lib/commands/list.js
CHANGED
|
@@ -1,76 +1,101 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* List command
|
|
3
|
-
*
|
|
3
|
+
* Enumerates available skills with a short description.
|
|
4
|
+
* Reads each skill's SKILL.md frontmatter — no hardcoded list.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import chalk from 'chalk';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
8
|
+
import { existsSync, readFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import {
|
|
11
|
+
SKILLS,
|
|
12
|
+
PACKAGE_VERSION,
|
|
13
|
+
getAgentsSkillsDir,
|
|
14
|
+
} from '../config.js';
|
|
11
15
|
|
|
12
|
-
const
|
|
16
|
+
const CHECK = chalk.green('✓');
|
|
17
|
+
const CROSS = chalk.red('✗');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract the skill's human-readable name and short description from SKILL.md.
|
|
21
|
+
* Returns { description, installed } where description is the first sentence of
|
|
22
|
+
* the frontmatter description, trimmed for terminal display.
|
|
23
|
+
*/
|
|
24
|
+
function readSkillMetadata(skillDir) {
|
|
25
|
+
const skillMd = join(skillDir, 'SKILL.md');
|
|
26
|
+
if (!existsSync(skillMd)) {
|
|
27
|
+
return { description: null, installed: false };
|
|
28
|
+
}
|
|
13
29
|
|
|
14
|
-
function parseSkillDescription(skillName) {
|
|
15
30
|
try {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (!match) return null;
|
|
20
|
-
|
|
21
|
-
const frontmatter = match[1];
|
|
22
|
-
|
|
23
|
-
// Extract name
|
|
24
|
-
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
25
|
-
const name = nameMatch ? nameMatch[1].trim() : skillName;
|
|
26
|
-
|
|
27
|
-
// Extract description (handles multi-line YAML >)
|
|
28
|
-
let description = '';
|
|
29
|
-
const descMatch = frontmatter.match(/description:\s*>\s*\n([\s\S]*?)(?=\n\w+:|$)/);
|
|
30
|
-
if (descMatch) {
|
|
31
|
-
description = descMatch[1]
|
|
32
|
-
.split('\n')
|
|
33
|
-
.map(line => line.trim())
|
|
34
|
-
.filter(Boolean)
|
|
35
|
-
.join(' ');
|
|
36
|
-
} else {
|
|
37
|
-
const singleMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
38
|
-
if (singleMatch) {
|
|
39
|
-
description = singleMatch[1].trim();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
31
|
+
const content = readFileSync(skillMd, 'utf8');
|
|
32
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
|
|
33
|
+
if (!frontmatter) return { description: null, installed: true };
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (description.length > maxLen) {
|
|
46
|
-
description = description.substring(0, maxLen - 1).trimEnd() + '…';
|
|
47
|
-
}
|
|
35
|
+
const descMatch = frontmatter[1].match(/description:\s*"([^"]+)"|description:\s*(.+)/);
|
|
36
|
+
if (!descMatch) return { description: null, installed: true };
|
|
48
37
|
|
|
49
|
-
|
|
38
|
+
const full = descMatch[1] || descMatch[2] || '';
|
|
39
|
+
let short;
|
|
40
|
+
const firstSentence = full.match(/^([^]*?\.)\s/);
|
|
41
|
+
if (firstSentence) {
|
|
42
|
+
short = firstSentence[1];
|
|
43
|
+
} else if (full.length > 80) {
|
|
44
|
+
const cut = full.slice(0, 80);
|
|
45
|
+
short = cut.slice(0, cut.lastIndexOf(' ')) + '…';
|
|
46
|
+
} else {
|
|
47
|
+
short = full;
|
|
48
|
+
}
|
|
49
|
+
return { description: short.trim(), installed: true };
|
|
50
50
|
} catch {
|
|
51
|
-
return null;
|
|
51
|
+
return { description: null, installed: true };
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
* List command handler
|
|
57
|
-
*/
|
|
58
55
|
export async function listCommand() {
|
|
59
56
|
console.log('');
|
|
60
|
-
console.log(chalk.bold.blue(
|
|
57
|
+
console.log(chalk.bold.blue(`AI Skills (v${PACKAGE_VERSION})`));
|
|
61
58
|
console.log('');
|
|
62
59
|
|
|
63
|
-
const
|
|
60
|
+
const skillsDir = getAgentsSkillsDir();
|
|
61
|
+
|
|
62
|
+
if (!existsSync(skillsDir)) {
|
|
63
|
+
console.log(chalk.yellow('No skills installed yet.'));
|
|
64
|
+
console.log('Install with:');
|
|
65
|
+
console.log(chalk.cyan(' aiskills install'));
|
|
66
|
+
console.log('');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const rows = [];
|
|
71
|
+
let installedCount = 0;
|
|
72
|
+
let maxNameLen = 0;
|
|
73
|
+
|
|
74
|
+
for (const name of SKILLS) {
|
|
75
|
+
const skillDir = join(skillsDir, name);
|
|
76
|
+
const { description, installed } = readSkillMetadata(skillDir);
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log(` ${name} ${desc}`);
|
|
78
|
+
if (installed) installedCount++;
|
|
79
|
+
if (name.length > maxNameLen) maxNameLen = name.length;
|
|
80
|
+
|
|
81
|
+
rows.push({ name, description, installed });
|
|
70
82
|
}
|
|
71
83
|
|
|
84
|
+
for (const row of rows) {
|
|
85
|
+
const mark = row.installed ? CHECK : CROSS;
|
|
86
|
+
const paddedName = row.name.padEnd(maxNameLen + 2);
|
|
87
|
+
const desc = row.description
|
|
88
|
+
? chalk.gray(row.description)
|
|
89
|
+
: chalk.gray(row.installed ? '(no description)' : 'not installed');
|
|
90
|
+
|
|
91
|
+
console.log(` ${mark} ${chalk.cyan(paddedName)} ${desc}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log(chalk.gray(`${installedCount}/${SKILLS.length} installed at ${skillsDir}`));
|
|
72
96
|
console.log('');
|
|
73
|
-
console.log(chalk.gray(
|
|
97
|
+
console.log(chalk.gray('Run `aiskills status` for installation health.'));
|
|
98
|
+
console.log(chalk.gray('Run `aiskills doctor` to diagnose issues.'));
|
|
74
99
|
console.log('');
|
|
75
100
|
}
|
|
76
101
|
|
package/lib/commands/skills.js
CHANGED
|
@@ -21,9 +21,11 @@ import {
|
|
|
21
21
|
removeSkillSymlinks,
|
|
22
22
|
removeLegacySkillSymlinks,
|
|
23
23
|
removeSkills,
|
|
24
|
+
removeCommands,
|
|
24
25
|
} from '../cleanup.js';
|
|
25
26
|
import {
|
|
26
27
|
installSkills,
|
|
28
|
+
installCommands,
|
|
27
29
|
getLocalRepoDir,
|
|
28
30
|
} from '../installer.js';
|
|
29
31
|
import { downloadRepoArchive } from '../downloader.js';
|
|
@@ -179,6 +181,24 @@ export async function skillsCommand(options) {
|
|
|
179
181
|
const skillsResult = await installSkills(repoDir, baseDir);
|
|
180
182
|
spinner.succeed(`${SKILLS.length} skill${SKILLS.length !== 1 ? 's' : ''} installed`);
|
|
181
183
|
|
|
184
|
+
// Install or remove slash commands based on Claude Code selection
|
|
185
|
+
if (selectedPlatformNames.has('claude')) {
|
|
186
|
+
spinner.start('Installing slash commands...');
|
|
187
|
+
const commandsResult = await installCommands(repoDir, baseDir);
|
|
188
|
+
if (commandsResult.installed.length > 0) {
|
|
189
|
+
spinner.succeed(
|
|
190
|
+
`${commandsResult.installed.length} slash command${commandsResult.installed.length !== 1 ? 's' : ''} installed`
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
spinner.info('No slash commands to install');
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
const removed = removeCommands(baseDir);
|
|
197
|
+
if (removed.removed.length > 0) {
|
|
198
|
+
console.log(chalk.green('✓'), `${removed.removed.length} slash commands removed`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
182
202
|
// Create symlinks for selected platforms
|
|
183
203
|
for (const platform of selectedPlatforms) {
|
|
184
204
|
removeLegacySkillSymlinks(platform.skillsDir);
|
|
@@ -198,6 +218,7 @@ export async function skillsCommand(options) {
|
|
|
198
218
|
}
|
|
199
219
|
} else {
|
|
200
220
|
const skillsResult = removeSkills(baseDir);
|
|
221
|
+
const commandsResult = removeCommands(baseDir);
|
|
201
222
|
const platformResults = detectedPlatforms.map((platform) => {
|
|
202
223
|
const symlinkResult = removeSkillSymlinks(platform.skillsDir);
|
|
203
224
|
return {
|
|
@@ -212,6 +233,10 @@ export async function skillsCommand(options) {
|
|
|
212
233
|
console.log(chalk.gray('ℹ'), 'No skills to remove');
|
|
213
234
|
}
|
|
214
235
|
|
|
236
|
+
if (commandsResult.removed.length > 0) {
|
|
237
|
+
console.log(chalk.green('✓'), `${commandsResult.removed.length} slash commands removed`);
|
|
238
|
+
}
|
|
239
|
+
|
|
215
240
|
for (const result of platformResults) {
|
|
216
241
|
if (result.removedCount > 0) {
|
|
217
242
|
console.log(chalk.green('✓'), `${result.displayName}: Skills unlinked`);
|
package/lib/commands/status.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function statusCommand() {
|
|
|
73
73
|
console.log(` Version: v${PACKAGE_VERSION}`);
|
|
74
74
|
console.log(` Skills: ${installedCount}/${totalCount} installed`);
|
|
75
75
|
console.log(` Hook: Claude Code SessionStart ${hookExists ? CHECK : CROSS}`);
|
|
76
|
-
console.log(` Last check: ${formatLastCheck(lastCheck)}`);
|
|
76
|
+
console.log(` Last npm check: ${formatLastCheck(lastCheck)}`);
|
|
77
77
|
|
|
78
78
|
// Platforms
|
|
79
79
|
console.log('');
|
|
@@ -13,14 +13,15 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
removeSkillSymlinks,
|
|
15
15
|
removeSkills,
|
|
16
|
+
removeCommands,
|
|
16
17
|
} from '../cleanup.js';
|
|
17
18
|
import { removeHook } from '../hooks.js';
|
|
18
19
|
import checkbox, { Separator } from '../prompts/checkboxCancel.js';
|
|
19
20
|
import { existsSync } from 'fs';
|
|
20
21
|
import { rm } from 'fs/promises';
|
|
21
22
|
import { join, resolve } from 'path';
|
|
22
|
-
import { getAgentsSkillsDir } from '../config.js';
|
|
23
|
-
import { getSkillList } from '../cleanup.js';
|
|
23
|
+
import { getAgentsSkillsDir, getClaudeCommandsDir } from '../config.js';
|
|
24
|
+
import { getSkillList, getCommandList } from '../cleanup.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Uninstall command handler
|
|
@@ -40,14 +41,19 @@ export async function uninstallCommand(options) {
|
|
|
40
41
|
const detectedPlatforms = detectPlatforms(baseDir);
|
|
41
42
|
|
|
42
43
|
const skillList = getSkillList();
|
|
44
|
+
const commandList = getCommandList().map((cmd) => `${cmd}.md`);
|
|
43
45
|
const homeSkillsDir = getAgentsSkillsDir();
|
|
44
46
|
const projectSkillsDir = getAgentsSkillsDir(projectDir);
|
|
47
|
+
const homeCommandsDir = getClaudeCommandsDir();
|
|
48
|
+
const projectCommandsDir = getClaudeCommandsDir(projectDir);
|
|
45
49
|
|
|
46
50
|
const hasAnyInDir = (dir, names) =>
|
|
47
51
|
!!dir && existsSync(dir) && names.some((name) => existsSync(join(dir, name)));
|
|
48
52
|
|
|
49
53
|
const hasHomeSkills = hasAnyInDir(homeSkillsDir, skillList);
|
|
50
54
|
const hasProjectSkills = options.local && hasAnyInDir(projectSkillsDir, skillList);
|
|
55
|
+
const hasHomeCommands = hasAnyInDir(homeCommandsDir, commandList);
|
|
56
|
+
const hasProjectCommands = options.local && hasAnyInDir(projectCommandsDir, commandList);
|
|
51
57
|
|
|
52
58
|
const hasHomeSymlinks = detectedPlatforms.some((platform) =>
|
|
53
59
|
hasAnyInDir(platform.skillsDir, skillList)
|
|
@@ -63,6 +69,12 @@ export async function uninstallCommand(options) {
|
|
|
63
69
|
if (hasProjectSkills) {
|
|
64
70
|
choices.push({ name: 'Skills from the `project` directory', value: 'skills-project', checked: false });
|
|
65
71
|
}
|
|
72
|
+
if (hasHomeCommands) {
|
|
73
|
+
choices.push({ name: 'Slash commands from `home` directory', value: 'commands-home', checked: true });
|
|
74
|
+
}
|
|
75
|
+
if (hasProjectCommands) {
|
|
76
|
+
choices.push({ name: 'Slash commands from `project` directory', value: 'commands-project', checked: false });
|
|
77
|
+
}
|
|
66
78
|
if (hasHomeSymlinks) {
|
|
67
79
|
choices.push({ name: 'Skill symlinks from `home` directory', value: 'symlinks-home', checked: true });
|
|
68
80
|
}
|
|
@@ -71,7 +83,7 @@ export async function uninstallCommand(options) {
|
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
if (choices.length === 0) {
|
|
74
|
-
console.log(chalk.yellow('No skills or symlinks found.'));
|
|
86
|
+
console.log(chalk.yellow('No skills, commands, or symlinks found.'));
|
|
75
87
|
console.log('');
|
|
76
88
|
return;
|
|
77
89
|
}
|
|
@@ -158,6 +170,28 @@ export async function uninstallCommand(options) {
|
|
|
158
170
|
}
|
|
159
171
|
}
|
|
160
172
|
|
|
173
|
+
if (targets.includes('commands-home')) {
|
|
174
|
+
spinner.start('Removing slash commands...');
|
|
175
|
+
const result = removeCommands(undefined);
|
|
176
|
+
if (result.removed.length > 0) {
|
|
177
|
+
spinner.succeed(`${result.removed.length} slash commands removed`);
|
|
178
|
+
actionTaken = true;
|
|
179
|
+
} else {
|
|
180
|
+
spinner.info('No slash commands to remove');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (targets.includes('commands-project')) {
|
|
185
|
+
spinner.start('Removing slash commands...');
|
|
186
|
+
const result = removeCommands(projectDir);
|
|
187
|
+
if (result.removed.length > 0) {
|
|
188
|
+
spinner.succeed(`${result.removed.length} slash commands removed`);
|
|
189
|
+
actionTaken = true;
|
|
190
|
+
} else {
|
|
191
|
+
spinner.info('No slash commands to remove');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
161
195
|
// Remove Claude Code SessionStart hook
|
|
162
196
|
const claudeDir = join(os.homedir(), '.claude');
|
|
163
197
|
removeHook(claudeDir);
|
package/lib/commands/update.js
CHANGED
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
REPO_URL,
|
|
11
11
|
SKILLS,
|
|
12
12
|
} from '../config.js';
|
|
13
|
+
import select from '../prompts/selectCancel.js';
|
|
13
14
|
import {
|
|
14
15
|
detectPlatforms,
|
|
15
16
|
} from '../platform.js';
|
|
16
17
|
import { cleanupLegacyArtifacts, getSkillList } from '../cleanup.js';
|
|
17
18
|
import {
|
|
18
19
|
installSkills,
|
|
20
|
+
installCommands,
|
|
19
21
|
getLocalRepoDir,
|
|
20
22
|
} from '../installer.js';
|
|
21
23
|
import {
|
|
@@ -26,9 +28,12 @@ import { createSkillSymlinks } from '../symlink.js';
|
|
|
26
28
|
import { getAgentsSkillsDir } from '../config.js';
|
|
27
29
|
import { existsSync } from 'fs';
|
|
28
30
|
import { join } from 'path';
|
|
31
|
+
import os from 'os';
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
34
|
* Check if a platform has any skill symlinks installed
|
|
35
|
+
* @param {string} platformSkillsDir - Platform skills directory
|
|
36
|
+
* @returns {boolean} True if any skill symlink exists
|
|
32
37
|
*/
|
|
33
38
|
function hasAnySkillSymlink(platformSkillsDir) {
|
|
34
39
|
if (!platformSkillsDir || !existsSync(platformSkillsDir)) return false;
|
|
@@ -38,6 +43,10 @@ function hasAnySkillSymlink(platformSkillsDir) {
|
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Perform the actual update for a specific scope
|
|
46
|
+
* @param {string|undefined} baseDir - Base directory (undefined = global, path = local)
|
|
47
|
+
* @param {string} repoDir - Repository directory
|
|
48
|
+
* @param {Object} spinner - Ora spinner instance
|
|
49
|
+
* @returns {Promise<void>}
|
|
41
50
|
*/
|
|
42
51
|
async function performUpdate(baseDir, repoDir, spinner) {
|
|
43
52
|
const detectedPlatforms = detectPlatforms(baseDir);
|
|
@@ -49,6 +58,20 @@ async function performUpdate(baseDir, repoDir, spinner) {
|
|
|
49
58
|
const skillsResult = await installSkills(repoDir, baseDir);
|
|
50
59
|
spinner.succeed(`${skillsResult.installed.length} skills updated`);
|
|
51
60
|
|
|
61
|
+
// Sync slash commands when Claude Code has skill symlinks installed
|
|
62
|
+
const claudePlatform = platformsWithSymlinks.find((p) => p.name === 'claude');
|
|
63
|
+
if (claudePlatform) {
|
|
64
|
+
spinner.start('Syncing slash commands...');
|
|
65
|
+
const commandsResult = await installCommands(repoDir, baseDir);
|
|
66
|
+
if (commandsResult.installed.length > 0) {
|
|
67
|
+
spinner.succeed(
|
|
68
|
+
`${commandsResult.installed.length} slash command${commandsResult.installed.length !== 1 ? 's' : ''} synced`
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
spinner.info('No slash commands to sync');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
52
75
|
cleanupLegacyArtifacts(baseDir);
|
|
53
76
|
|
|
54
77
|
for (const platform of platformsWithSymlinks) {
|
|
@@ -62,6 +85,7 @@ async function performUpdate(baseDir, repoDir, spinner) {
|
|
|
62
85
|
|
|
63
86
|
/**
|
|
64
87
|
* Update command handler
|
|
88
|
+
* @param {Object} options - Command options
|
|
65
89
|
*/
|
|
66
90
|
export async function updateCommand(options) {
|
|
67
91
|
console.log('');
|
|
@@ -70,27 +94,74 @@ export async function updateCommand(options) {
|
|
|
70
94
|
|
|
71
95
|
const spinner = ora();
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
let baseDir = options.local ? process.cwd() : undefined;
|
|
98
|
+
const hasSkillsAt = (dir) =>
|
|
99
|
+
SKILLS.some((skill) => existsSync(join(getAgentsSkillsDir(dir), skill)));
|
|
100
|
+
|
|
101
|
+
if (!options.local) {
|
|
102
|
+
const projectDir = process.cwd();
|
|
103
|
+
// When cwd === home, "local" and "global" point to the same .agents/skills dir
|
|
104
|
+
const isHomeDir = projectDir === os.homedir();
|
|
105
|
+
const hasLocalSkills = !isHomeDir && hasSkillsAt(projectDir);
|
|
106
|
+
const hasGlobalSkills = hasSkillsAt(undefined);
|
|
107
|
+
|
|
108
|
+
if (hasLocalSkills && hasGlobalSkills) {
|
|
109
|
+
try {
|
|
110
|
+
const scope = await select({
|
|
111
|
+
message: 'Both local and global skills detected. What do you want to update:',
|
|
112
|
+
choices: [
|
|
113
|
+
{ name: 'Global skills (user home)', value: 'global' },
|
|
114
|
+
{ name: 'Local skills (current project)', value: 'local' },
|
|
115
|
+
{ name: 'Both locations', value: 'both' },
|
|
116
|
+
],
|
|
117
|
+
theme: {
|
|
118
|
+
style: {
|
|
119
|
+
answer: () => '',
|
|
120
|
+
prefix: () => chalk.cyan('?'),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
if (scope === 'cancel') {
|
|
125
|
+
console.log('Cancelled.');
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
if (scope === 'local') {
|
|
129
|
+
baseDir = projectDir;
|
|
130
|
+
} else if (scope === 'both') {
|
|
131
|
+
baseDir = 'both';
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.log('\nCancelled.');
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
} else if (hasLocalSkills && !hasGlobalSkills) {
|
|
138
|
+
baseDir = projectDir;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
74
141
|
|
|
75
|
-
if (baseDir) {
|
|
142
|
+
if (baseDir === 'both') {
|
|
143
|
+
console.log(chalk.cyan('Mode: Updating both global and local skills'));
|
|
144
|
+
} else if (baseDir) {
|
|
76
145
|
console.log(chalk.cyan('Mode: Local update (current project)'));
|
|
77
146
|
} else {
|
|
78
147
|
console.log(chalk.cyan('Mode: Global update (user home)'));
|
|
79
148
|
}
|
|
80
149
|
console.log('');
|
|
81
150
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
151
|
+
if (baseDir !== 'both') {
|
|
152
|
+
const skillsDir = getAgentsSkillsDir(baseDir);
|
|
153
|
+
const hasSkillsInstalled = skillsDir && SKILLS.some((skill) => existsSync(join(skillsDir, skill)));
|
|
154
|
+
if (!hasSkillsInstalled) {
|
|
155
|
+
console.log(chalk.yellow('No skills installed at this location.'));
|
|
156
|
+
console.log('Install them first with:');
|
|
157
|
+
console.log(' aiskills install');
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log('Looked for skills in:');
|
|
160
|
+
console.log(` ${baseDir ? 'Local' : 'Global'}: ${skillsDir}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
91
163
|
}
|
|
92
164
|
|
|
93
|
-
// Check for updates
|
|
94
165
|
spinner.start('Checking for updates...');
|
|
95
166
|
|
|
96
167
|
try {
|
|
@@ -101,7 +172,7 @@ export async function updateCommand(options) {
|
|
|
101
172
|
try {
|
|
102
173
|
latestVersion = await fetchLatestNpmVersion();
|
|
103
174
|
} catch {
|
|
104
|
-
// Ignore
|
|
175
|
+
// Ignore error, we already know there's an update
|
|
105
176
|
}
|
|
106
177
|
|
|
107
178
|
spinner.warn('New version available');
|
|
@@ -130,7 +201,16 @@ export async function updateCommand(options) {
|
|
|
130
201
|
return;
|
|
131
202
|
}
|
|
132
203
|
|
|
133
|
-
|
|
204
|
+
if (baseDir === 'both') {
|
|
205
|
+
console.log(chalk.bold('Updating global skills...'));
|
|
206
|
+
await performUpdate(undefined, repoDir, spinner);
|
|
207
|
+
console.log('');
|
|
208
|
+
|
|
209
|
+
console.log(chalk.bold('Updating local skills...'));
|
|
210
|
+
await performUpdate(process.cwd(), repoDir, spinner);
|
|
211
|
+
} else {
|
|
212
|
+
await performUpdate(baseDir, repoDir, spinner);
|
|
213
|
+
}
|
|
134
214
|
|
|
135
215
|
console.log('');
|
|
136
216
|
console.log(chalk.green('✓ Update complete!'));
|
package/lib/config.js
CHANGED
|
@@ -36,12 +36,21 @@ export const SKILLS = [
|
|
|
36
36
|
// Legacy skills to remove during updates/uninstall
|
|
37
37
|
export const LEGACY_SKILLS = [];
|
|
38
38
|
|
|
39
|
+
// Slash commands to install (Claude Code only — copied to ~/.claude/commands/)
|
|
40
|
+
export const COMMANDS = [
|
|
41
|
+
'release',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Legacy commands to remove during updates/uninstall
|
|
45
|
+
export const LEGACY_COMMANDS = [];
|
|
46
|
+
|
|
39
47
|
// Cache/config directory
|
|
40
48
|
export const getConfigDir = () => path.join(os.homedir(), '.aiskills');
|
|
41
49
|
|
|
42
50
|
// Directory paths
|
|
43
51
|
export const getAgentsSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.agents', 'skills');
|
|
44
52
|
export const getClaudeSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.claude', 'skills');
|
|
53
|
+
export const getClaudeCommandsDir = (baseDir = os.homedir()) => path.join(baseDir, '.claude', 'commands');
|
|
45
54
|
export const getGeminiSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.gemini', 'skills');
|
|
46
55
|
export const getCodexSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.codex', 'skills');
|
|
47
56
|
|
|
@@ -80,9 +89,12 @@ export default {
|
|
|
80
89
|
REPO_API_URL,
|
|
81
90
|
SKILLS,
|
|
82
91
|
LEGACY_SKILLS,
|
|
92
|
+
COMMANDS,
|
|
93
|
+
LEGACY_COMMANDS,
|
|
83
94
|
getConfigDir,
|
|
84
95
|
getAgentsSkillsDir,
|
|
85
96
|
getClaudeSkillsDir,
|
|
97
|
+
getClaudeCommandsDir,
|
|
86
98
|
getGeminiSkillsDir,
|
|
87
99
|
getCodexSkillsDir,
|
|
88
100
|
getPlatforms,
|
package/lib/downloader.js
CHANGED
|
@@ -11,6 +11,39 @@ import { unlink } from 'fs/promises';
|
|
|
11
11
|
import { extract } from 'tar';
|
|
12
12
|
import { REPO_API_URL, REPO_RAW_URL, GITHUB_API_HEADERS } from './config.js';
|
|
13
13
|
|
|
14
|
+
function getTestLatestNpmVersion() {
|
|
15
|
+
const value = process.env.AISKILLS_TEST_NPM_LATEST_VERSION;
|
|
16
|
+
return value && value.trim() ? value.trim() : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetch latest release info from GitHub API
|
|
21
|
+
* @returns {Promise<Object>} Release information
|
|
22
|
+
*/
|
|
23
|
+
export async function fetchLatestRelease() {
|
|
24
|
+
const response = await fetch(
|
|
25
|
+
`${REPO_API_URL}/releases/latest`,
|
|
26
|
+
{
|
|
27
|
+
headers: GITHUB_API_HEADERS,
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`Failed to fetch release info: ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return response.json();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetch latest version from GitHub
|
|
40
|
+
* @returns {Promise<string>} Latest version tag
|
|
41
|
+
*/
|
|
42
|
+
export async function fetchLatestVersion() {
|
|
43
|
+
const release = await fetchLatestRelease();
|
|
44
|
+
return release.tag_name;
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
/**
|
|
15
48
|
* Download a file from URL to local path
|
|
16
49
|
* @param {string} url - URL to download
|
|
@@ -59,11 +92,34 @@ export async function downloadRepoArchive(destDir, ref = 'main') {
|
|
|
59
92
|
}
|
|
60
93
|
}
|
|
61
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Download a single file from GitHub raw content
|
|
97
|
+
* @param {string} filePath - Path in repository
|
|
98
|
+
* @param {string} destPath - Local destination path
|
|
99
|
+
* @param {string} ref - Git ref (branch, tag, commit)
|
|
100
|
+
* @returns {Promise<void>}
|
|
101
|
+
*/
|
|
102
|
+
export async function downloadRawFile(filePath, destPath, ref = 'main') {
|
|
103
|
+
const url = `${REPO_RAW_URL}/${ref}/${filePath}`;
|
|
104
|
+
|
|
105
|
+
const dir = dirname(destPath);
|
|
106
|
+
if (!existsSync(dir)) {
|
|
107
|
+
mkdirSync(dir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await downloadFile(url, destPath);
|
|
111
|
+
}
|
|
112
|
+
|
|
62
113
|
/**
|
|
63
114
|
* Fetch latest version from npm registry
|
|
64
115
|
* @returns {Promise<string>} Latest version number
|
|
65
116
|
*/
|
|
66
117
|
export async function fetchLatestNpmVersion() {
|
|
118
|
+
const testVersion = getTestLatestNpmVersion();
|
|
119
|
+
if (testVersion) {
|
|
120
|
+
return testVersion;
|
|
121
|
+
}
|
|
122
|
+
|
|
67
123
|
const response = await fetch('https://registry.npmjs.org/@maccesar/aiskills');
|
|
68
124
|
|
|
69
125
|
if (!response.ok) {
|
|
@@ -103,8 +159,11 @@ export async function checkForUpdate(currentVersion) {
|
|
|
103
159
|
}
|
|
104
160
|
|
|
105
161
|
export default {
|
|
162
|
+
fetchLatestRelease,
|
|
163
|
+
fetchLatestVersion,
|
|
106
164
|
downloadFile,
|
|
107
165
|
downloadRepoArchive,
|
|
166
|
+
downloadRawFile,
|
|
108
167
|
fetchLatestNpmVersion,
|
|
109
168
|
checkForUpdate,
|
|
110
169
|
};
|
package/lib/installer.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File installation utilities
|
|
3
|
-
* Installs skills to their respective directories
|
|
3
|
+
* Installs skills and slash commands to their respective directories
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
copyFileSync,
|
|
7
8
|
existsSync,
|
|
8
9
|
mkdirSync,
|
|
9
10
|
} from 'fs';
|
|
@@ -12,9 +13,11 @@ import { remove, copy } from 'fs-extra';
|
|
|
12
13
|
import os from 'os';
|
|
13
14
|
import {
|
|
14
15
|
SKILLS,
|
|
16
|
+
COMMANDS,
|
|
15
17
|
getAgentsSkillsDir,
|
|
18
|
+
getClaudeCommandsDir,
|
|
16
19
|
} from './config.js';
|
|
17
|
-
import { removeSkills } from './cleanup.js';
|
|
20
|
+
import { removeSkills, removeCommands } from './cleanup.js';
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* Recursively copy a directory
|
|
@@ -91,6 +94,68 @@ export async function installSkills(repoDir, baseDir = os.homedir()) {
|
|
|
91
94
|
return results;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Install a single slash command to the Claude commands directory
|
|
99
|
+
* @param {string} repoDir - Repository directory
|
|
100
|
+
* @param {string} commandName - Name of the command (without .md)
|
|
101
|
+
* @param {string} baseDir - Base directory for installation
|
|
102
|
+
* @returns {Promise<boolean>} True if installed successfully
|
|
103
|
+
*/
|
|
104
|
+
export async function installCommand(repoDir, commandName, baseDir = os.homedir()) {
|
|
105
|
+
const commandsDir = getClaudeCommandsDir(baseDir);
|
|
106
|
+
const src = join(repoDir, 'commands', `${commandName}.md`);
|
|
107
|
+
const dest = join(commandsDir, `${commandName}.md`);
|
|
108
|
+
|
|
109
|
+
if (!existsSync(commandsDir)) {
|
|
110
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!existsSync(src)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (existsSync(dest)) {
|
|
118
|
+
await remove(dest);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
copyFileSync(src, dest);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Install all slash commands to the Claude commands directory
|
|
127
|
+
* @param {string} repoDir - Repository directory
|
|
128
|
+
* @param {string} baseDir - Base directory for installation
|
|
129
|
+
* @returns {Promise<Object>} Results object with success/failure counts
|
|
130
|
+
*/
|
|
131
|
+
export async function installCommands(repoDir, baseDir = os.homedir()) {
|
|
132
|
+
const results = {
|
|
133
|
+
installed: [],
|
|
134
|
+
failed: [],
|
|
135
|
+
removed: [],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const legacyLocal = removeCommands(baseDir, { legacyOnly: true });
|
|
139
|
+
results.removed.push(...legacyLocal.removed);
|
|
140
|
+
results.failed.push(...legacyLocal.failed);
|
|
141
|
+
|
|
142
|
+
if (baseDir && baseDir !== os.homedir()) {
|
|
143
|
+
const legacyGlobal = removeCommands(undefined, { legacyOnly: true });
|
|
144
|
+
results.removed.push(...legacyGlobal.removed);
|
|
145
|
+
results.failed.push(...legacyGlobal.failed);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const cmd of COMMANDS) {
|
|
149
|
+
if (await installCommand(repoDir, cmd, baseDir)) {
|
|
150
|
+
results.installed.push(cmd);
|
|
151
|
+
} else {
|
|
152
|
+
results.failed.push(cmd);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return results;
|
|
157
|
+
}
|
|
158
|
+
|
|
94
159
|
/**
|
|
95
160
|
* Get the local repository directory if running from source
|
|
96
161
|
* @returns {string|null} Local repo directory or null
|
|
@@ -110,5 +175,7 @@ export default {
|
|
|
110
175
|
copyDirectory,
|
|
111
176
|
installSkill,
|
|
112
177
|
installSkills,
|
|
178
|
+
installCommand,
|
|
179
|
+
installCommands,
|
|
113
180
|
getLocalRepoDir,
|
|
114
181
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: refactoring-ui
|
|
3
|
-
description:
|
|
4
|
-
Design advisor based exclusively on "Refactoring UI" by Adam Wathan & Steve Schoger.
|
|
5
|
-
Use when the user asks for UI/UX design advice, design reviews, visual hierarchy
|
|
6
|
-
improvements, color system help, typography guidance, spacing decisions, depth/shadow
|
|
7
|
-
usage, image handling, or finishing touches on any interface.
|
|
3
|
+
description: Design advisor based exclusively on "Refactoring UI" by Adam Wathan & Steve Schoger. Use when the user asks for UI/UX design advice, design reviews, visual hierarchy improvements, color system help, typography guidance, spacing decisions, depth/shadow usage, image handling, or finishing touches on any interface.
|
|
8
4
|
when_to_use: >
|
|
9
5
|
- User asks "how do I make this look better?"
|
|
10
6
|
- User asks about color palettes, type scales, or spacing systems
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stitch-showcase
|
|
3
|
-
description:
|
|
4
|
-
Use this skill whenever the user has Google Stitch design exports and wants to do anything with them —
|
|
5
|
-
build a gallery, organize screens, generate a navigable showcase, process zip files, or browse designs.
|
|
6
|
-
Triggers on: "organiza mis diseños de Stitch", "arma el muestrario", "organize my Stitch designs",
|
|
7
|
-
"build the showcase", "I have the Stitch zips in X", "process this design folder",
|
|
8
|
-
"generate the index for my Stitch screens", "tengo los zips de Stitch", "quiero ver mis pantallas",
|
|
9
|
-
or any mention of Stitch exports, screen.png + code.html pairs, or design zip files.
|
|
10
|
-
Even if the user just says "I have a zip from Stitch" or "mis exports de Stitch" — use this skill.
|
|
11
|
-
Also triggers on: "optimiza el showcase", "mejora las descripciones", "enrich the showcase",
|
|
12
|
-
"optimize titles", "optimiza", or any request to improve an existing showcase's content.
|
|
3
|
+
description: Use this skill whenever the user has Google Stitch design exports and wants to do anything with them — build a gallery, organize screens, generate a navigable showcase, process zip files, or browse designs. Triggers on "organiza mis diseños de Stitch", "arma el muestrario", "organize my Stitch designs", "build the showcase", "I have the Stitch zips in X", "process this design folder", "generate the index for my Stitch screens", "tengo los zips de Stitch", "quiero ver mis pantallas", or any mention of Stitch exports, screen.png + code.html pairs, or design zip files. Even if the user just says "I have a zip from Stitch" or "mis exports de Stitch" — use this skill. Also triggers on "optimiza el showcase", "mejora las descripciones", "enrich the showcase", "optimize titles", "optimiza", or any request to improve an existing showcase's content.
|
|
13
4
|
---
|
|
14
5
|
|
|
15
6
|
# stitch-showcase
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vscode-extension-dev
|
|
3
|
-
description:
|
|
4
|
-
Guide for building VS Code extensions from scratch. Use when the user is creating,
|
|
5
|
-
scaffolding, designing, debugging, testing, bundling, or publishing a VS Code extension.
|
|
6
|
-
Covers all major API patterns: TreeView, QuickPick, Webview, StatusBar, commands,
|
|
7
|
-
configuration, SecretStorage, progress indicators, and esbuild bundling.
|
|
3
|
+
description: Guide for building VS Code extensions from scratch. Use when the user is creating, scaffolding, designing, debugging, testing, bundling, or publishing a VS Code extension. Covers all major API patterns — TreeView, QuickPick, Webview, StatusBar, commands, configuration, SecretStorage, progress indicators, and esbuild bundling.
|
|
8
4
|
when_to_use: >
|
|
9
5
|
- User wants to create a new VS Code extension
|
|
10
6
|
- User asks about VS Code extension APIs (TreeView, Webview, QuickPick, etc.)
|