@maccesar/aiskills 1.12.0 → 1.16.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 +89 -9
- package/lib/cleanup.js +29 -0
- package/lib/commands/skills.js +110 -9
- package/lib/config.js +18 -9
- package/lib/installer.js +5 -3
- package/lib/symlink.js +45 -3
- package/lib/utils.js +41 -0
- package/package.json +2 -2
- package/skills/audit-codebase/SKILL.md +70 -0
- package/skills/audit-codebase/agents/openai.yaml +4 -0
- package/skills/audit-codebase/references/comprehensive-audit.md +220 -0
- package/skills/audit-codebase/references/report-format.md +119 -0
- package/skills/humaniza/SKILL.md +55 -4
- package/skills/humaniza/references/ai-patterns-es.md +40 -0
- package/skills/humaniza/references/checklist.md +9 -0
- package/skills/humaniza/references/examples.md +16 -0
- package/skills/humaniza/references/lexicon-es-mx.md +18 -0
- package/skills/humaniza/references/structures-es.md +132 -0
- package/skills/humaniza/scripts/check_ai_patterns.py +216 -0
- package/skills/refactoring-ui/SKILL.md +65 -29
- package/skills/refactoring-ui/references/05-motion.md +124 -0
- package/skills/refactoring-ui/references/06-dark-mode.md +117 -0
- package/skills/refactoring-ui/references/07-component-patterns.md +181 -0
- package/skills/session-log/SKILL.md +524 -0
- package/skills/session-log/evals/README.md +79 -0
- package/skills/session-log/evals/ab-ronda-1.md +115 -0
- package/skills/session-log/evals/ab-ronda-2.md +174 -0
- package/skills/session-log/evals/defecto-experimento.md +12 -0
- package/skills/session-log/evals/evals.json +147 -0
- package/skills/session-log/evals/trigger-eval.json +23 -0
- package/skills/session-log/references/file-layout.md +336 -0
- package/skills/session-log/references/verification.md +173 -0
- package/skills/stitch-showcase/SKILL.md +24 -232
- package/skills/stitch-showcase/references/07-theme-system.md +12 -0
- package/skills/stitch-showcase/references/08-type-detection.md +9 -1
- package/skills/stitch-showcase/references/10-component-standardization.md +25 -0
- package/skills/stitch-showcase/references/12-video-embedding.md +113 -0
- package/skills/stitch-showcase/references/13-language-detection.md +82 -0
- package/skills/stitch-showcase/references/14-troubleshooting-known-issues.md +122 -0
- package/skills/stitch-showcase/references/15-build-flags.md +71 -0
- package/skills/stitch-showcase/references/16-design-md-format.md +107 -0
- package/skills/stitch-showcase/references/index.html +25 -19
- package/skills/stitch-showcase/references/viewer.html +24 -12
- package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/slug_demangle.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/build_showcase.py +150 -10
- package/skills/stitch-showcase/scripts/parse_design_md.py +145 -12
- package/skills/stitch-showcase/scripts/slug_demangle.py +209 -0
- package/skills/vscode-extension-dev/SKILL.md +90 -41
- package/skills/vscode-extension-dev/references/api-additional.md +168 -0
- package/skills/vscode-extension-dev/references/api-progress.md +55 -0
- package/skills/vscode-extension-dev/references/api-quickpick.md +75 -0
- package/skills/vscode-extension-dev/references/api-secretstorage.md +57 -0
- package/skills/vscode-extension-dev/references/api-statusbar.md +38 -0
- package/skills/vscode-extension-dev/references/api-treeview.md +78 -0
- package/skills/vscode-extension-dev/references/api-webview.md +149 -0
- package/skills/vscode-extension-dev/references/architecture.md +67 -0
- package/skills/vscode-extension-dev/references/debugger.md +179 -0
- package/skills/vscode-extension-dev/references/lsp.md +175 -0
- package/skills/vscode-extension-dev/references/notebooks.md +208 -0
- package/skills/vscode-extension-dev/references/testing.md +208 -0
- package/skills/vscode-extension-dev/references/api-patterns.md +0 -625
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ claude # or gemini, or codex
|
|
|
40
40
|
|
|
41
41
|
Installed files:
|
|
42
42
|
- All skills to `~/.agents/skills/`
|
|
43
|
-
- Platform symlinks in `~/.claude/skills/` and
|
|
43
|
+
- Platform symlinks in `~/.claude/skills/` (Gemini CLI and Codex CLI auto-discover from `~/.agents/skills/` — no platform-specific symlink needed)
|
|
44
44
|
|
|
45
45
|
### Which option should I use?
|
|
46
46
|
|
|
@@ -58,7 +58,7 @@ Installed files:
|
|
|
58
58
|
| Platform | Status | Installation Path |
|
|
59
59
|
| --------------------------------------------------------- | --------- | ------------------- |
|
|
60
60
|
| [Claude Code](https://claude.ai/claude-code) | Supported | `~/.claude/skills/` |
|
|
61
|
-
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported | `~/.
|
|
61
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported | `~/.agents/skills/` (auto-discovered) |
|
|
62
62
|
| [Codex CLI](https://developers.openai.com/codex/cli/) | Supported | `~/.agents/skills/` (auto-discovered) |
|
|
63
63
|
|
|
64
64
|
All three platforms use the same Agent Skills format: a `SKILL.md` file with YAML frontmatter that tells the assistant when to use the skill and what to do.
|
|
@@ -69,10 +69,12 @@ All three platforms use the same Agent Skills format: a `SKILL.md` file with YAM
|
|
|
69
69
|
|
|
70
70
|
| Skill | Domain | Source | Reference Files |
|
|
71
71
|
| -------------------- | ------------ | ------------------------------------ | --------------- |
|
|
72
|
-
| refactoring-ui | Design | "Refactoring UI" by Wathan & Schoger |
|
|
73
|
-
| humaniza | Writing (es) | Curated Spanish/es-MX style rules |
|
|
74
|
-
|
|
|
75
|
-
|
|
|
72
|
+
| refactoring-ui | Design | "Refactoring UI" by Wathan & Schoger | 7 files |
|
|
73
|
+
| humaniza | Writing (es) | Curated Spanish/es-MX style rules | 7 files |
|
|
74
|
+
| audit-codebase | Auditing | Evidence-based audit methodology | 2 files |
|
|
75
|
+
| vscode-extension-dev | VS Code | VS Code Extension API docs | 14 files |
|
|
76
|
+
| stitch-showcase | Design Tools | Google Stitch export workflow | 16 files |
|
|
77
|
+
| session-log | Project | Convention + 3 A/B rounds | 2 files |
|
|
76
78
|
|
|
77
79
|
Use `aiskills list` to see available skills from the command line. Pull requests are welcome.
|
|
78
80
|
|
|
@@ -132,8 +134,6 @@ Hard restrictions:
|
|
|
132
134
|
Distribution note:
|
|
133
135
|
- 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.
|
|
134
136
|
|
|
135
|
-
---
|
|
136
|
-
|
|
137
137
|
## How skills work
|
|
138
138
|
|
|
139
139
|
Skills activate based on what you ask. You can write prompts normally:
|
|
@@ -192,7 +192,7 @@ Reference files:
|
|
|
192
192
|
|
|
193
193
|
### humaniza
|
|
194
194
|
|
|
195
|
-
An editor for Spanish text (es-MX). It removes common AI writing patterns and rewrites the text so it sounds natural without changing the meaning.
|
|
195
|
+
An editor for Spanish text (es-MX). Based on curated es-MX style rules and adapted techniques from Hardik Pandya's Stop Slop skill (cut filler, break formulaic structures, active voice, specificity, varied rhythm). It removes common AI writing patterns and rewrites the text so it sounds natural without changing the meaning.
|
|
196
196
|
|
|
197
197
|
When it activates:
|
|
198
198
|
- User asks to "humanize" a text in Spanish
|
|
@@ -224,6 +224,7 @@ Reference files:
|
|
|
224
224
|
| ----------------- | --------------------------------------------------------------- |
|
|
225
225
|
| ai-patterns-es.md | AI writing tics in Spanish: inflated phrases, filler, templates |
|
|
226
226
|
| lexicon-es-mx.md | Preferred es-MX vocabulary vs. Spain Spanish |
|
|
227
|
+
| structures-es.md | Structural patterns to avoid: binary contrasts, false agency... |
|
|
227
228
|
| modes-es-mx.md | Rules per mode (marketing, technical, support, etc.) |
|
|
228
229
|
| voice-es-mx.md | How to add human voice: rhythm, concreteness, variety |
|
|
229
230
|
| checklist.md | Final QA before delivering the text |
|
|
@@ -238,6 +239,39 @@ Scope:
|
|
|
238
239
|
|
|
239
240
|
---
|
|
240
241
|
|
|
242
|
+
### audit-codebase
|
|
243
|
+
|
|
244
|
+
A senior software auditor that reviews an entire project — architecture, security, compatibility, performance, maintainability, and tests — and delivers an evidence-based diagnosis with a decision matrix and correction plan. Works in two stages: a read-only audit first, then an authorized implementation of the approved plan. Every restrictive security recommendation must name the legitimate use cases it could break and the less-restrictive alternative that was evaluated. The report is written in the user's language.
|
|
245
|
+
|
|
246
|
+
When it activates:
|
|
247
|
+
- Asking for a full technical audit or security review of a project
|
|
248
|
+
- Checking if a project is ready for production
|
|
249
|
+
- Reviewing architecture, dependencies, compatibility, or test coverage
|
|
250
|
+
- Implementing an approved audit correction plan
|
|
251
|
+
|
|
252
|
+
Example prompts:
|
|
253
|
+
```
|
|
254
|
+
"Audit this project end to end"
|
|
255
|
+
"Is this backend ready for production?"
|
|
256
|
+
"Do a security review without breaking the anonymous flows"
|
|
257
|
+
"Find real problems before we ship"
|
|
258
|
+
"Implement the audit plan we approved"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Reference files:
|
|
262
|
+
| File | Topics |
|
|
263
|
+
| ---------------------- | --------------------------------------------------------------------------- |
|
|
264
|
+
| comprehensive-audit.md | Mandatory principles, 24-area technical scope, 5-phase method, severity |
|
|
265
|
+
| report-format.md | Executive summary, findings table, decision matrix, correction plan formats |
|
|
266
|
+
|
|
267
|
+
Scope:
|
|
268
|
+
- Stage 1 never modifies files; the first deliverable is always diagnosis + plan
|
|
269
|
+
- Every confirmed finding gets an explicit disposition (fix now, later, accept, won't fix)
|
|
270
|
+
- Findings are classified as Confirmed / Conditional risk / Unverified — no pattern-matched "vulnerabilities" without evidence
|
|
271
|
+
- Stage 2 only starts after the user approves the decision matrix, and implements it completely
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
241
275
|
### vscode-extension-dev
|
|
242
276
|
|
|
243
277
|
A guide for building VS Code extensions from scaffolding to publishing. Based on the official VS Code Extension API docs.
|
|
@@ -273,6 +307,52 @@ Reference files:
|
|
|
273
307
|
|
|
274
308
|
---
|
|
275
309
|
|
|
310
|
+
### session-log
|
|
311
|
+
|
|
312
|
+
Gives a project one predictable place for its working state, so both you and any assistant know where to look instead of hunting through scattered notes. It installs a fixed four-file convention under `docs/project/` and writes a short pointer into every context file the repo has — `CLAUDE.md`, `AGENTS.md`, `GEMINI.md` — so the notes stay findable no matter which assistant opens the project next.
|
|
313
|
+
|
|
314
|
+
The convention:
|
|
315
|
+
|
|
316
|
+
| File | Holds | Loaded at startup |
|
|
317
|
+
| --- | --- | --- |
|
|
318
|
+
| `status.md` | Where the work stands: half-done things, next step, what's blocked, deployment state | **No** |
|
|
319
|
+
| `requirements.md` | What the system must do, and the acceptance criterion for each item | Yes |
|
|
320
|
+
| `decisions.md` | What was chosen and why. Append-only, dated | Yes |
|
|
321
|
+
| `context.md` | Documentation map, architecture, conventions, traps | Yes |
|
|
322
|
+
|
|
323
|
+
**Why `status.md` is excluded from startup.** Cached context is matched as a prefix — the first byte that differs invalidates everything after it. Status written inside a startup-loaded file means every update throws away the cache for all the stable content behind it. The file you edit most often is the one that must not load at startup.
|
|
324
|
+
|
|
325
|
+
How to use it — just say it, in whatever words you'd use anyway:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
"set up the project notes here — the mobile app lives at ../../Apps/MyApp"
|
|
329
|
+
"ya me voy, déjame anotado dónde quedé"
|
|
330
|
+
"where did we leave off? I haven't touched this repo in weeks"
|
|
331
|
+
"my CLAUDE.md has the progress and a date inside it — should I move that?"
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Closing a session and resuming one are different jobs and it treats them differently. On the way out it writes; on the way back in it reads `status.md` and then checks it against the repo before repeating it to you — what landed since the file was written, whether the branch it names still exists, what's uncommitted that it never mentioned. A three-week-old note is a snapshot, and the most expensive way to use one is to trust it.
|
|
335
|
+
|
|
336
|
+
There is no slash command, by design: a command and a skill doing the same job means two copies of the logic that drift apart, and a command only works in Claude Code. This is one file that Claude, Codex and Gemini all read the same way — point any of them at `skills/session-log/SKILL.md` if it doesn't pick it up on its own.
|
|
337
|
+
|
|
338
|
+
Once the convention is installed, finding the notes no longer depends on the skill at all — the pointer in `CLAUDE.md`, `AGENTS.md` and `GEMINI.md` is what any assistant reads at startup.
|
|
339
|
+
|
|
340
|
+
What it will not do:
|
|
341
|
+
- Commit, tag, push, or write CHANGELOG entries — that is a release, and releasing assumes the work is finished, which is the opposite of why this exists. Use `/release` for that.
|
|
342
|
+
- Edit your uncommitted code. It reports what it finds broken and leaves it alone.
|
|
343
|
+
- Invent a completion percentage. Without a fixed denominator any number is made up, so it counts what is enumerable or describes status in words.
|
|
344
|
+
- Write a token, a password or a client's private details into the files. They get committed, and a secret deleted in a later commit is still in the history — it records where the credential lives instead.
|
|
345
|
+
- Overwrite the record on arrival. If a resumed file turns out to be badly out of date it says so and offers; rewriting is your call.
|
|
346
|
+
|
|
347
|
+
Measured behaviour, across three A/B rounds against a no-skill baseline (18 runs, adversarially graded):
|
|
348
|
+
|
|
349
|
+
| | With skill | Without |
|
|
350
|
+
| --- | --- | --- |
|
|
351
|
+
| Kept volatile status out of the startup chain | 9 / 9 | 0 / 9 |
|
|
352
|
+
| Left the user's broken uncommitted code untouched | yes | no — fixed it unasked |
|
|
353
|
+
|
|
354
|
+
Token cost is 3–13% higher per run. **Those rounds graded an earlier layout** — a single status file versus an imported memory index — so what they establish is the split itself, not the four filenames. The paths added since (resuming against a stale file, upgrading an earlier install, monorepos, a gitignored `docs/`) have prompts written for them and have not been run. The grading notes, and an explicit account of what is and isn't measured, are in `skills/session-log/evals/`.
|
|
355
|
+
|
|
276
356
|
### stitch-showcase
|
|
277
357
|
|
|
278
358
|
A workflow skill for processing Google Stitch design exports. It handles the full lifecycle: from raw zips to a navigable showcase, component standardization, and a visual component catalog.
|
package/lib/cleanup.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getAgentsSkillsDir,
|
|
11
11
|
getClaudeCommandsDir,
|
|
12
12
|
getCodexSkillsDir,
|
|
13
|
+
getGeminiSkillsDir,
|
|
13
14
|
} from './config.js';
|
|
14
15
|
import { detectPlatforms } from './platform.js';
|
|
15
16
|
import { existsSync, lstatSync, rmSync } from 'fs';
|
|
@@ -54,6 +55,20 @@ export function removeLegacySkillSymlinks(platformSkillsDir) {
|
|
|
54
55
|
return removeSkillSymlinks(platformSkillsDir, { legacyOnly: true });
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
// When the user de-selects some skills during an install, anything previously
|
|
59
|
+
// installed for those skills (in ~/.agents/skills/ and as platform symlinks)
|
|
60
|
+
// must be cleaned up so the on-disk state matches the user's selection.
|
|
61
|
+
export function removeUnselectedSkills(baseDir, selectedSkills) {
|
|
62
|
+
const skillsDir = getAgentsSkillsDir(baseDir);
|
|
63
|
+
const unselected = SKILLS.filter((s) => !selectedSkills.includes(s));
|
|
64
|
+
return removeEntriesAtDir(skillsDir, unselected, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function removeUnselectedSymlinks(platformSkillsDir, selectedSkills) {
|
|
68
|
+
const unselected = SKILLS.filter((s) => !selectedSkills.includes(s));
|
|
69
|
+
return removeEntriesAtDir(platformSkillsDir, unselected, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
// Codex reads skills from the canonical ~/.agents/skills/, so any aiskills-managed
|
|
58
73
|
// symlinks at ~/.codex/skills/ from earlier versions are redundant and should be
|
|
59
74
|
// cleaned up. Targets both active and legacy skill names.
|
|
@@ -62,6 +77,15 @@ export function removeCodexRedundantSymlinks(baseDir) {
|
|
|
62
77
|
return removeSkillSymlinks(codexSkillsDir);
|
|
63
78
|
}
|
|
64
79
|
|
|
80
|
+
// Gemini CLI also auto-discovers skills from ~/.agents/skills/ per the
|
|
81
|
+
// agentskills.io standard. Symlinks at ~/.gemini/skills/ from earlier
|
|
82
|
+
// versions produce "Skill conflict detected" warnings on Gemini startup
|
|
83
|
+
// because the same skill is found in both locations.
|
|
84
|
+
export function removeGeminiRedundantSymlinks(baseDir) {
|
|
85
|
+
const geminiSkillsDir = getGeminiSkillsDir(baseDir);
|
|
86
|
+
return removeSkillSymlinks(geminiSkillsDir);
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
export function removeSkills(baseDir, options = {}) {
|
|
66
90
|
const skillsDir = getAgentsSkillsDir(baseDir);
|
|
67
91
|
const skillList = getSkillList(options);
|
|
@@ -84,6 +108,7 @@ export function cleanupLegacyArtifacts(baseDir) {
|
|
|
84
108
|
}
|
|
85
109
|
|
|
86
110
|
removeCodexRedundantSymlinks(baseDir);
|
|
111
|
+
removeGeminiRedundantSymlinks(baseDir);
|
|
87
112
|
|
|
88
113
|
if (baseDir) {
|
|
89
114
|
removeSkills(undefined, { legacyOnly: true });
|
|
@@ -93,6 +118,7 @@ export function cleanupLegacyArtifacts(baseDir) {
|
|
|
93
118
|
removeLegacySkillSymlinks(platform.skillsDir);
|
|
94
119
|
}
|
|
95
120
|
removeCodexRedundantSymlinks(undefined);
|
|
121
|
+
removeGeminiRedundantSymlinks(undefined);
|
|
96
122
|
}
|
|
97
123
|
}
|
|
98
124
|
|
|
@@ -102,6 +128,9 @@ export default {
|
|
|
102
128
|
removeSkillSymlinks,
|
|
103
129
|
removeLegacySkillSymlinks,
|
|
104
130
|
removeCodexRedundantSymlinks,
|
|
131
|
+
removeGeminiRedundantSymlinks,
|
|
132
|
+
removeUnselectedSkills,
|
|
133
|
+
removeUnselectedSymlinks,
|
|
105
134
|
removeSkills,
|
|
106
135
|
removeCommands,
|
|
107
136
|
cleanupLegacyArtifacts,
|
package/lib/commands/skills.js
CHANGED
|
@@ -22,6 +22,9 @@ import {
|
|
|
22
22
|
removeLegacySkillSymlinks,
|
|
23
23
|
removeSkills,
|
|
24
24
|
removeCommands,
|
|
25
|
+
removeUnselectedSkills,
|
|
26
|
+
removeUnselectedSymlinks,
|
|
27
|
+
cleanupLegacyArtifacts,
|
|
25
28
|
} from '../cleanup.js';
|
|
26
29
|
import {
|
|
27
30
|
installSkills,
|
|
@@ -31,6 +34,7 @@ import {
|
|
|
31
34
|
import { downloadRepoArchive } from '../downloader.js';
|
|
32
35
|
import { createSkillSymlinks } from '../symlink.js';
|
|
33
36
|
import { installHook } from '../hooks.js';
|
|
37
|
+
import { readSkillDescription, shortenSkillDescription } from '../utils.js';
|
|
34
38
|
import { mkdtemp } from 'fs/promises';
|
|
35
39
|
import { existsSync } from 'fs';
|
|
36
40
|
import { join, resolve } from 'path';
|
|
@@ -64,6 +68,22 @@ export async function skillsCommand(options) {
|
|
|
64
68
|
} else {
|
|
65
69
|
console.log(chalk.cyan('Mode: Global installation (user home)'));
|
|
66
70
|
}
|
|
71
|
+
const skillsLocation = isLocal
|
|
72
|
+
? './.agents/skills/'
|
|
73
|
+
: (customPath ? `${baseDir}/.agents/skills/` : '~/.agents/skills/');
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(
|
|
76
|
+
chalk.yellow('▸'),
|
|
77
|
+
'Skills install to',
|
|
78
|
+
chalk.cyan.bold(skillsLocation),
|
|
79
|
+
chalk.dim('(agentskills.io standard)')
|
|
80
|
+
);
|
|
81
|
+
console.log(
|
|
82
|
+
' ' + chalk.dim('Read directly by Gemini, Codex, Cursor, Cline, Amp, GitHub Copilot, +more.')
|
|
83
|
+
);
|
|
84
|
+
console.log(
|
|
85
|
+
' ' + chalk.dim('Below: extra symlink mirrors for Claude Code.')
|
|
86
|
+
);
|
|
67
87
|
console.log('');
|
|
68
88
|
|
|
69
89
|
// Detect installed platforms at the target base directory
|
|
@@ -85,9 +105,9 @@ export async function skillsCommand(options) {
|
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
if (detectedPlatforms.length === 0 && !options.path && !isLocal) {
|
|
88
|
-
console.log(chalk.yellow('No AI coding assistants detected globally.'));
|
|
89
|
-
console.log('Install
|
|
90
|
-
console.log('(
|
|
108
|
+
console.log(chalk.yellow('No AI coding assistants detected globally that need platform symlinks.'));
|
|
109
|
+
console.log('Install Claude Code if you want aiskills to create skill symlinks for it.');
|
|
110
|
+
console.log('(Gemini CLI and Codex CLI auto-discover skills from ~/.agents/skills/ — no platform-specific symlink needed.)');
|
|
91
111
|
console.log('Or use: aiskills install --local');
|
|
92
112
|
process.exit(1);
|
|
93
113
|
}
|
|
@@ -173,14 +193,73 @@ export async function skillsCommand(options) {
|
|
|
173
193
|
}
|
|
174
194
|
}
|
|
175
195
|
|
|
196
|
+
// Skill selection prompt — pre-checks all skills; the user can uncheck the
|
|
197
|
+
// ones they don't want. Skipped under --all (CI/automation) and removeOnly.
|
|
198
|
+
// Each line shows the skill name + a one-liner hint in dim gray, like
|
|
199
|
+
// skills.sh, so the rendered row height stays constant and the list does
|
|
200
|
+
// not "jump" when the cursor moves between skills with different-length
|
|
201
|
+
// descriptions.
|
|
202
|
+
let skillsToInstall = SKILLS;
|
|
203
|
+
if (!removeOnly && !options.all && !options.path) {
|
|
204
|
+
const skillChoices = SKILLS.map((name) => {
|
|
205
|
+
const fullDescription = readSkillDescription(repoDir, name);
|
|
206
|
+
const hint = shortenSkillDescription(fullDescription);
|
|
207
|
+
const label = hint
|
|
208
|
+
? `${name} ${chalk.dim(`(${hint})`)}`
|
|
209
|
+
: name;
|
|
210
|
+
return { name: label, value: name, short: name, checked: true };
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const selected = await checkbox({
|
|
215
|
+
message: 'Select skills to install:',
|
|
216
|
+
choices: [...skillChoices, new Separator(' ')],
|
|
217
|
+
theme: {
|
|
218
|
+
style: {
|
|
219
|
+
renderSelectedChoices: () => '',
|
|
220
|
+
prefix: () => chalk.cyan('?'),
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (selected.includes('cancel')) {
|
|
226
|
+
console.log('Cancelled.');
|
|
227
|
+
process.exit(0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
skillsToInstall = SKILLS.filter((s) => selected.includes(s));
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.log('\nCancelled.');
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (skillsToInstall.length === 0) {
|
|
237
|
+
console.log(chalk.yellow('No skills selected. Nothing to install.'));
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
176
242
|
const selectedPlatformNames = new Set(selectedPlatforms.map((platform) => platform.name));
|
|
177
243
|
|
|
178
244
|
try {
|
|
179
245
|
if (!removeOnly) {
|
|
180
246
|
// Install skills
|
|
181
247
|
spinner.start('Installing skills...');
|
|
182
|
-
const skillsResult = await installSkills(repoDir, baseDir);
|
|
183
|
-
spinner.succeed(`${
|
|
248
|
+
const skillsResult = await installSkills(repoDir, baseDir, skillsToInstall);
|
|
249
|
+
spinner.succeed(`${skillsToInstall.length} skill${skillsToInstall.length !== 1 ? 's' : ''} installed`);
|
|
250
|
+
|
|
251
|
+
// If the user deselected some skills, sweep any previously-installed
|
|
252
|
+
// copies and their platform symlinks so on-disk state matches selection.
|
|
253
|
+
if (skillsToInstall.length !== SKILLS.length) {
|
|
254
|
+
removeUnselectedSkills(baseDir, skillsToInstall);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Sweep legacy skills, legacy commands, and redundant symlinks at platforms
|
|
258
|
+
// that no longer need aiskills-managed mirrors (Gemini, Codex). Without
|
|
259
|
+
// this, users that ran older aiskills versions keep stale symlinks at
|
|
260
|
+
// ~/.gemini/skills/ and ~/.codex/skills/ that Gemini reports as "Skill
|
|
261
|
+
// conflict detected" warnings on startup.
|
|
262
|
+
cleanupLegacyArtifacts(baseDir);
|
|
184
263
|
|
|
185
264
|
// Install or remove slash commands based on Claude Code selection
|
|
186
265
|
if (selectedPlatformNames.has('claude')) {
|
|
@@ -203,17 +282,20 @@ export async function skillsCommand(options) {
|
|
|
203
282
|
// Create symlinks for selected platforms
|
|
204
283
|
for (const platform of selectedPlatforms) {
|
|
205
284
|
removeLegacySkillSymlinks(platform.skillsDir);
|
|
285
|
+
if (skillsToInstall.length !== SKILLS.length) {
|
|
286
|
+
removeUnselectedSymlinks(platform.skillsDir, skillsToInstall);
|
|
287
|
+
}
|
|
206
288
|
spinner.start(`Linking ${platform.displayName}...`);
|
|
207
289
|
const symlinkResult = await createSkillSymlinks(
|
|
208
290
|
platform.skillsDir,
|
|
209
|
-
|
|
291
|
+
skillsToInstall,
|
|
210
292
|
baseDir
|
|
211
293
|
);
|
|
212
|
-
if (symlinkResult.linked.length ===
|
|
294
|
+
if (symlinkResult.linked.length === skillsToInstall.length) {
|
|
213
295
|
spinner.succeed(`${platform.displayName}: Skills linked`);
|
|
214
296
|
} else {
|
|
215
297
|
spinner.warn(
|
|
216
|
-
`${platform.displayName}: ${symlinkResult.linked.length}/${
|
|
298
|
+
`${platform.displayName}: ${symlinkResult.linked.length}/${skillsToInstall.length} skills linked`
|
|
217
299
|
);
|
|
218
300
|
}
|
|
219
301
|
}
|
|
@@ -269,9 +351,28 @@ export async function skillsCommand(options) {
|
|
|
269
351
|
}
|
|
270
352
|
}
|
|
271
353
|
|
|
272
|
-
// Summary
|
|
354
|
+
// Summary — show where the skills landed and which agents read them.
|
|
355
|
+
// This is the "did it actually work" confirmation the user needs.
|
|
273
356
|
console.log('');
|
|
274
357
|
console.log(chalk.green('✓ Skills sync complete!'));
|
|
358
|
+
if (!removeOnly) {
|
|
359
|
+
console.log('');
|
|
360
|
+
console.log(
|
|
361
|
+
' ' + chalk.bold(skillsLocation) + chalk.dim(' (') +
|
|
362
|
+
chalk.dim(skillsToInstall.length + ' skill' + (skillsToInstall.length !== 1 ? 's' : '')) +
|
|
363
|
+
chalk.dim(')')
|
|
364
|
+
);
|
|
365
|
+
console.log(
|
|
366
|
+
' ' + chalk.dim('• universal: ') +
|
|
367
|
+
'Gemini, Codex, Cursor, Cline, Amp, GitHub Copilot ' + chalk.dim('+more')
|
|
368
|
+
);
|
|
369
|
+
if (selectedPlatformNames.has('claude')) {
|
|
370
|
+
console.log(
|
|
371
|
+
' ' + chalk.dim('• symlinked: ') +
|
|
372
|
+
'Claude Code ' + chalk.dim('(~/.claude/skills/)')
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
275
376
|
console.log('');
|
|
276
377
|
|
|
277
378
|
if (!removeOnly && !isLocal && detectOS() === 'windows') {
|
package/lib/config.js
CHANGED
|
@@ -27,8 +27,10 @@ export const REPO_API_URL = 'https://api.github.com/repos/macCesar/aiskills';
|
|
|
27
27
|
|
|
28
28
|
// Skills to install
|
|
29
29
|
export const SKILLS = [
|
|
30
|
+
'audit-codebase',
|
|
30
31
|
'humaniza',
|
|
31
32
|
'refactoring-ui',
|
|
33
|
+
'session-log',
|
|
32
34
|
'stitch-showcase',
|
|
33
35
|
'vscode-extension-dev',
|
|
34
36
|
];
|
|
@@ -54,7 +56,23 @@ export const getClaudeCommandsDir = (baseDir = os.homedir()) => path.join(baseDi
|
|
|
54
56
|
export const getGeminiSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.gemini', 'skills');
|
|
55
57
|
export const getCodexSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.codex', 'skills');
|
|
56
58
|
|
|
59
|
+
// Name of the Claude marketplace and plugin where this CLI publishes itself.
|
|
60
|
+
// Used to detect when a skill is already installed via the marketplace plugin
|
|
61
|
+
// and avoid creating a duplicate symlink in ~/.claude/skills/.
|
|
62
|
+
export const CLAUDE_PLUGIN_MARKETPLACE = 'maccesar-aiskills';
|
|
63
|
+
export const CLAUDE_PLUGIN_NAME = 'aiskills';
|
|
64
|
+
export const getClaudePluginSkillsPath = (baseDir = os.homedir()) =>
|
|
65
|
+
path.join(baseDir, '.claude', 'plugins', 'cache', CLAUDE_PLUGIN_MARKETPLACE, CLAUDE_PLUGIN_NAME);
|
|
66
|
+
|
|
57
67
|
// AI platform detection
|
|
68
|
+
//
|
|
69
|
+
// Only platforms that need aiskills-managed symlinks appear here.
|
|
70
|
+
// Gemini CLI and Codex CLI auto-discover skills from the canonical
|
|
71
|
+
// ~/.agents/skills/ per the agentskills.io standard, so creating
|
|
72
|
+
// platform-specific symlinks at ~/.gemini/skills/ or ~/.codex/skills/
|
|
73
|
+
// would be redundant — and in Gemini's case actively harmful, since it
|
|
74
|
+
// reads both locations and reports "Skill conflict detected" warnings
|
|
75
|
+
// when the same skill exists in both.
|
|
58
76
|
export const getPlatforms = (baseDir = os.homedir()) => [
|
|
59
77
|
{
|
|
60
78
|
name: 'claude',
|
|
@@ -62,15 +80,6 @@ export const getPlatforms = (baseDir = os.homedir()) => [
|
|
|
62
80
|
skillsDir: getClaudeSkillsDir(baseDir),
|
|
63
81
|
configDir: path.join(baseDir, '.claude'),
|
|
64
82
|
},
|
|
65
|
-
{
|
|
66
|
-
name: 'gemini',
|
|
67
|
-
displayName: 'Gemini CLI',
|
|
68
|
-
skillsDir: getGeminiSkillsDir(baseDir),
|
|
69
|
-
configDir: path.join(baseDir, '.gemini'),
|
|
70
|
-
},
|
|
71
|
-
// Codex CLI is intentionally omitted: it auto-discovers skills from the
|
|
72
|
-
// canonical ~/.agents/skills/ per the agentskills.io standard, so a
|
|
73
|
-
// platform-specific symlink at ~/.codex/skills/ would be redundant.
|
|
74
83
|
];
|
|
75
84
|
|
|
76
85
|
// API configuration
|
package/lib/installer.js
CHANGED
|
@@ -61,12 +61,14 @@ export async function installSkill(repoDir, skillName, baseDir = os.homedir()) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Install
|
|
64
|
+
* Install skills to the agents skills directory
|
|
65
65
|
* @param {string} repoDir - Repository directory
|
|
66
66
|
* @param {string} baseDir - Base directory for installation
|
|
67
|
+
* @param {string[]} [skillsToInstall] - Subset of skills to install (defaults to all SKILLS)
|
|
67
68
|
* @returns {Promise<Object>} Results object with success/failure counts
|
|
68
69
|
*/
|
|
69
|
-
export async function installSkills(repoDir, baseDir = os.homedir()) {
|
|
70
|
+
export async function installSkills(repoDir, baseDir = os.homedir(), skillsToInstall) {
|
|
71
|
+
const list = skillsToInstall || SKILLS;
|
|
70
72
|
const results = {
|
|
71
73
|
installed: [],
|
|
72
74
|
failed: [],
|
|
@@ -83,7 +85,7 @@ export async function installSkills(repoDir, baseDir = os.homedir()) {
|
|
|
83
85
|
results.failed.push(...legacyGlobal.failed);
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
for (const skill of
|
|
88
|
+
for (const skill of list) {
|
|
87
89
|
if (await installSkill(repoDir, skill, baseDir)) {
|
|
88
90
|
results.installed.push(skill);
|
|
89
91
|
} else {
|
package/lib/symlink.js
CHANGED
|
@@ -69,19 +69,49 @@ async function removePath(path) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* Check if a skill is already available via the maccesar-aiskills Claude marketplace plugin.
|
|
73
|
+
* When the plugin is installed, Claude Code lists the skill from
|
|
74
|
+
* ~/.claude/plugins/cache/maccesar-aiskills/aiskills/<version>/skills/<skill>/
|
|
75
|
+
* and an additional symlink at ~/.claude/skills/<skill> produces a duplicate
|
|
76
|
+
* entry in the slash-command autocomplete. This helper lets the symlink step
|
|
77
|
+
* skip Claude when the plugin already covers it.
|
|
78
|
+
* @param {string} skillName - Skill name (e.g. 'stitch-showcase')
|
|
79
|
+
* @param {string} baseDir - Optional base directory (defaults to homedir via config)
|
|
80
|
+
* @returns {Promise<boolean>} True if the plugin provides this skill
|
|
81
|
+
*/
|
|
82
|
+
export async function isClaudePluginSkillInstalled(skillName, baseDir) {
|
|
83
|
+
const { readdir } = await import('fs/promises');
|
|
84
|
+
const { getClaudePluginSkillsPath } = await import('./config.js');
|
|
85
|
+
const pluginBase = getClaudePluginSkillsPath(baseDir);
|
|
86
|
+
if (!existsSync(pluginBase)) return false;
|
|
87
|
+
try {
|
|
88
|
+
const versions = await readdir(pluginBase);
|
|
89
|
+
return versions.some((v) => existsSync(join(pluginBase, v, 'skills', skillName)));
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create symlinks for all skills to a platform directory.
|
|
97
|
+
* For the Claude platform, skills already provided by the maccesar-aiskills
|
|
98
|
+
* marketplace plugin are skipped to avoid duplicate slash-command entries;
|
|
99
|
+
* if a stale symlink from a previous install exists, it is removed.
|
|
73
100
|
* @param {string} platformSkillsDir - Platform skills directory
|
|
74
101
|
* @param {Array} skills - List of skill names
|
|
75
102
|
* @param {string} baseDir - Optional base directory for target resolution
|
|
76
|
-
* @returns {Promise<Object>} Results object with
|
|
103
|
+
* @returns {Promise<Object>} Results object with linked / failed / skipped arrays
|
|
77
104
|
*/
|
|
78
105
|
export async function createSkillSymlinks(platformSkillsDir, skills, baseDir) {
|
|
79
|
-
const { getAgentsSkillsDir } = await import('./config.js');
|
|
106
|
+
const { getAgentsSkillsDir, getClaudeSkillsDir } = await import('./config.js');
|
|
80
107
|
const agentsSkillsDir = getAgentsSkillsDir(baseDir);
|
|
108
|
+
const claudeSkillsDir = getClaudeSkillsDir(baseDir);
|
|
109
|
+
const isClaudePlatform = platformSkillsDir === claudeSkillsDir;
|
|
81
110
|
|
|
82
111
|
const results = {
|
|
83
112
|
linked: [],
|
|
84
113
|
failed: [],
|
|
114
|
+
skipped: [],
|
|
85
115
|
};
|
|
86
116
|
|
|
87
117
|
// Ensure platform directory exists
|
|
@@ -93,6 +123,17 @@ export async function createSkillSymlinks(platformSkillsDir, skills, baseDir) {
|
|
|
93
123
|
const useRelative = !!baseDir;
|
|
94
124
|
|
|
95
125
|
for (const skill of skills) {
|
|
126
|
+
// Skip Claude when the marketplace plugin already provides this skill;
|
|
127
|
+
// clean up any stale symlink left from a previous CLI install.
|
|
128
|
+
if (isClaudePlatform && await isClaudePluginSkillInstalled(skill, baseDir)) {
|
|
129
|
+
const stalePath = join(platformSkillsDir, skill);
|
|
130
|
+
if (existsSync(stalePath)) {
|
|
131
|
+
await removePath(stalePath);
|
|
132
|
+
}
|
|
133
|
+
results.skipped.push(skill);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
96
137
|
const target = join(agentsSkillsDir, skill);
|
|
97
138
|
const linkPath = join(platformSkillsDir, skill);
|
|
98
139
|
|
|
@@ -148,6 +189,7 @@ export async function updateSymlink(target, path) {
|
|
|
148
189
|
export default {
|
|
149
190
|
createSymlinkOrCopy,
|
|
150
191
|
createSkillSymlinks,
|
|
192
|
+
isClaudePluginSkillInstalled,
|
|
151
193
|
isSymlink,
|
|
152
194
|
resolveSymlink,
|
|
153
195
|
updateSymlink,
|
package/lib/utils.js
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
* Utility functions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read the description field from a skill's SKILL.md frontmatter.
|
|
10
|
+
* @param {string} repoDir - Repository directory containing skills/
|
|
11
|
+
* @param {string} skillName - Skill name (directory under skills/)
|
|
12
|
+
* @returns {string} Description text, or empty string if not available.
|
|
13
|
+
*/
|
|
14
|
+
export function readSkillDescription(repoDir, skillName) {
|
|
15
|
+
try {
|
|
16
|
+
const skillPath = join(repoDir, 'skills', skillName, 'SKILL.md');
|
|
17
|
+
const content = readFileSync(skillPath, 'utf8');
|
|
18
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
19
|
+
if (!fmMatch) return '';
|
|
20
|
+
const descMatch = fmMatch[1].match(/^description:\s*["']?(.+?)["']?\s*$/m);
|
|
21
|
+
return descMatch ? descMatch[1].trim() : '';
|
|
22
|
+
} catch {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compress a skill description to a one-line summary suitable for an inline
|
|
29
|
+
* checkbox preview. Picks the first segment of the description and trims it
|
|
30
|
+
* to `maxLen` characters, appending an ellipsis when the source is longer.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} description - The full SKILL.md description.
|
|
33
|
+
* @param {number} maxLen - Maximum characters of the output (excluding ellipsis).
|
|
34
|
+
* @returns {string} Trimmed one-liner, or empty string if input was empty.
|
|
35
|
+
*/
|
|
36
|
+
export function shortenSkillDescription(description, maxLen = 60) {
|
|
37
|
+
if (!description) return '';
|
|
38
|
+
let firstSegment = description.split(/\s*[—.:;]\s*/)[0].trim();
|
|
39
|
+
firstSegment = firstSegment.replace(/\s+/g, ' ');
|
|
40
|
+
if (firstSegment.length <= maxLen) return firstSegment;
|
|
41
|
+
return firstSegment.slice(0, maxLen).trimEnd() + '…';
|
|
42
|
+
}
|
|
43
|
+
|
|
5
44
|
/**
|
|
6
45
|
* Format a list of items for display
|
|
7
46
|
* @param {Array} items - Array of strings
|
|
@@ -43,6 +82,8 @@ export function compareVersions(v1, v2) {
|
|
|
43
82
|
}
|
|
44
83
|
|
|
45
84
|
export default {
|
|
85
|
+
readSkillDescription,
|
|
86
|
+
shortenSkillDescription,
|
|
46
87
|
formatList,
|
|
47
88
|
parseVersion,
|
|
48
89
|
compareVersions,
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maccesar/aiskills",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "AI coding assistant skills for Claude Code, Gemini CLI, and Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"aiskills": "./bin/aiskills.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "node --test test
|
|
10
|
+
"test": "node --test test/*.test.js",
|
|
11
11
|
"lint": "eslint lib/**/*.js",
|
|
12
12
|
"format": "prettier --write lib/**/*.js"
|
|
13
13
|
},
|