@maccesar/aiskills 1.15.0 → 1.16.1
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 +47 -2
- package/lib/claude-plugin.js +105 -0
- package/lib/commands/doctor.js +50 -3
- package/lib/commands/skills.js +19 -4
- package/lib/commands/update.js +4 -0
- package/lib/config.js +12 -0
- package/lib/installer.js +18 -0
- package/lib/symlink.js +5 -10
- package/package.json +2 -2
- 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/README.md
CHANGED
|
@@ -74,6 +74,7 @@ All three platforms use the same Agent Skills format: a `SKILL.md` file with YAM
|
|
|
74
74
|
| audit-codebase | Auditing | Evidence-based audit methodology | 2 files |
|
|
75
75
|
| vscode-extension-dev | VS Code | VS Code Extension API docs | 14 files |
|
|
76
76
|
| stitch-showcase | Design Tools | Google Stitch export workflow | 16 files |
|
|
77
|
+
| session-log | Project | Convention + 3 A/B rounds | 2 files |
|
|
77
78
|
|
|
78
79
|
Use `aiskills list` to see available skills from the command line. Pull requests are welcome.
|
|
79
80
|
|
|
@@ -133,8 +134,6 @@ Hard restrictions:
|
|
|
133
134
|
Distribution note:
|
|
134
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.
|
|
135
136
|
|
|
136
|
-
---
|
|
137
|
-
|
|
138
137
|
## How skills work
|
|
139
138
|
|
|
140
139
|
Skills activate based on what you ask. You can write prompts normally:
|
|
@@ -308,6 +307,52 @@ Reference files:
|
|
|
308
307
|
|
|
309
308
|
---
|
|
310
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
|
+
|
|
311
356
|
### stitch-showcase
|
|
312
357
|
|
|
313
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.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of the maccesar-aiskills Claude Code marketplace plugin.
|
|
3
|
+
*
|
|
4
|
+
* When the plugin is installed, Claude Code already lists our skills and slash
|
|
5
|
+
* commands from its plugin cache, so the CLI must not also install its own copy
|
|
6
|
+
* into ~/.claude/ — that produces duplicate entries in the autocomplete.
|
|
7
|
+
*
|
|
8
|
+
* The subtlety, and the reason this lives in its own module: **the cache on disk
|
|
9
|
+
* does not mean the plugin is installed.** Uninstalling a plugin removes it from
|
|
10
|
+
* `enabledPlugins` in settings.json but leaves the cache directory behind.
|
|
11
|
+
* Treating that leftover directory as proof of installation makes the CLI skip
|
|
12
|
+
* work it should do, which leaves Claude Code with no skills at all and no way
|
|
13
|
+
* for the user to repair it by re-running install. So the question we answer here
|
|
14
|
+
* is "is the plugin enabled AND does it carry this file", never just the latter.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import {
|
|
20
|
+
CLAUDE_PLUGIN_KEY,
|
|
21
|
+
getClaudePluginSkillsPath,
|
|
22
|
+
getClaudeSettingsPaths,
|
|
23
|
+
} from './config.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether Claude Code currently has the aiskills plugin enabled.
|
|
27
|
+
* @param {string} baseDir - Optional base directory (defaults to homedir via config)
|
|
28
|
+
* @returns {boolean} True only when a settings file explicitly enables the plugin
|
|
29
|
+
*/
|
|
30
|
+
export function isClaudePluginEnabled(baseDir) {
|
|
31
|
+
for (const settingsPath of getClaudeSettingsPaths(baseDir)) {
|
|
32
|
+
try {
|
|
33
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
34
|
+
if (settings?.enabledPlugins?.[CLAUDE_PLUGIN_KEY] === true) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Missing or malformed settings: nothing here says the plugin is enabled.
|
|
39
|
+
// Falling through to `false` is the safe direction — the cost of a wrong
|
|
40
|
+
// `false` is a duplicate entry, the cost of a wrong `true` is a user with
|
|
41
|
+
// no skills installed.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether a plugin cache directory exists at all, regardless of whether the
|
|
49
|
+
* plugin is enabled. An enabled plugin implies a cache; a cache implies nothing,
|
|
50
|
+
* because uninstalling leaves it behind. Diagnostics use this to tell "never
|
|
51
|
+
* installed" apart from "uninstalled, leftovers on disk".
|
|
52
|
+
* @param {string} baseDir - Optional base directory
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
export function hasClaudePluginCache(baseDir) {
|
|
56
|
+
return existsSync(getClaudePluginSkillsPath(baseDir));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether any cached version of the plugin carries the given file.
|
|
61
|
+
* @param {string} kind - Subdirectory inside the plugin ('skills' or 'commands')
|
|
62
|
+
* @param {string} entry - Entry to look for (skill directory or command file)
|
|
63
|
+
* @param {string} baseDir - Optional base directory
|
|
64
|
+
* @returns {boolean} True if a cached version contains it
|
|
65
|
+
*/
|
|
66
|
+
function pluginCacheContains(kind, entry, baseDir) {
|
|
67
|
+
const pluginBase = getClaudePluginSkillsPath(baseDir);
|
|
68
|
+
if (!existsSync(pluginBase)) return false;
|
|
69
|
+
try {
|
|
70
|
+
return readdirSync(pluginBase).some((version) =>
|
|
71
|
+
existsSync(join(pluginBase, version, kind, entry))
|
|
72
|
+
);
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whether the installed plugin already provides this skill to Claude Code.
|
|
80
|
+
* @param {string} skillName - Skill name (e.g. 'session-log')
|
|
81
|
+
* @param {string} baseDir - Optional base directory
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
export function pluginProvidesSkill(skillName, baseDir) {
|
|
85
|
+
return isClaudePluginEnabled(baseDir) && pluginCacheContains('skills', skillName, baseDir);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Whether the installed plugin already provides this slash command.
|
|
90
|
+
* @param {string} commandName - Command name without the .md extension
|
|
91
|
+
* @param {string} baseDir - Optional base directory
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
export function pluginProvidesCommand(commandName, baseDir) {
|
|
95
|
+
return (
|
|
96
|
+
isClaudePluginEnabled(baseDir) && pluginCacheContains('commands', `${commandName}.md`, baseDir)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default {
|
|
101
|
+
isClaudePluginEnabled,
|
|
102
|
+
hasClaudePluginCache,
|
|
103
|
+
pluginProvidesSkill,
|
|
104
|
+
pluginProvidesCommand,
|
|
105
|
+
};
|
package/lib/commands/doctor.js
CHANGED
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
} from '../config.js';
|
|
17
17
|
import { hasHook } from '../hooks.js';
|
|
18
18
|
import { readLastCheck } from '../cache.js';
|
|
19
|
+
import {
|
|
20
|
+
isClaudePluginEnabled,
|
|
21
|
+
hasClaudePluginCache,
|
|
22
|
+
pluginProvidesSkill,
|
|
23
|
+
} from '../claude-plugin.js';
|
|
19
24
|
|
|
20
25
|
const CHECK = chalk.green('✓');
|
|
21
26
|
const CROSS = chalk.red('✗');
|
|
@@ -79,8 +84,18 @@ export async function doctorCommand() {
|
|
|
79
84
|
for (const platform of platforms) {
|
|
80
85
|
const missing = [];
|
|
81
86
|
const broken = [];
|
|
87
|
+
const servedByPlugin = [];
|
|
82
88
|
|
|
83
89
|
for (const skill of SKILLS) {
|
|
90
|
+
// A skill the marketplace plugin provides is *supposed* to have no mirror
|
|
91
|
+
// here — the CLI removes it on purpose to avoid a duplicate entry. Counting
|
|
92
|
+
// it as missing turns a healthy marketplace install into a wall of errors
|
|
93
|
+
// telling the user to run a command that will correctly do nothing.
|
|
94
|
+
if (platform.name === 'claude' && pluginProvidesSkill(skill, homeDir)) {
|
|
95
|
+
servedByPlugin.push(skill);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
const linkPath = join(platform.skillsDir, skill);
|
|
85
100
|
try {
|
|
86
101
|
const stat = lstatSync(linkPath);
|
|
@@ -96,15 +111,26 @@ export async function doctorCommand() {
|
|
|
96
111
|
}
|
|
97
112
|
}
|
|
98
113
|
|
|
99
|
-
const
|
|
114
|
+
const expected = SKILLS.length - servedByPlugin.length;
|
|
115
|
+
const linkedCount = expected - missing.length - broken.length;
|
|
116
|
+
const pluginNote =
|
|
117
|
+
servedByPlugin.length > 0
|
|
118
|
+
? chalk.dim(` (+${servedByPlugin.length} served by the marketplace plugin)`)
|
|
119
|
+
: '';
|
|
100
120
|
|
|
101
121
|
if (missing.length === 0 && broken.length === 0) {
|
|
102
|
-
|
|
122
|
+
const summary =
|
|
123
|
+
expected === 0
|
|
124
|
+
? `all ${servedByPlugin.length} skills served by the marketplace plugin`
|
|
125
|
+
: `${linkedCount}/${expected} skills linked${pluginNote}`;
|
|
126
|
+
console.log(` ${CHECK} ${platform.displayName}: ${summary}`);
|
|
103
127
|
} else {
|
|
104
128
|
const problems = [];
|
|
105
129
|
if (missing.length > 0) problems.push(`missing: ${missing.join(', ')}`);
|
|
106
130
|
if (broken.length > 0) problems.push(`broken: ${broken.join(', ')}`);
|
|
107
|
-
console.log(
|
|
131
|
+
console.log(
|
|
132
|
+
` ${CROSS} ${platform.displayName}: ${linkedCount}/${expected} skills linked${pluginNote} (${problems.join('; ')})`
|
|
133
|
+
);
|
|
108
134
|
issues += missing.length + broken.length;
|
|
109
135
|
|
|
110
136
|
// Collect symlink issues for detailed report
|
|
@@ -117,6 +143,27 @@ export async function doctorCommand() {
|
|
|
117
143
|
}
|
|
118
144
|
}
|
|
119
145
|
|
|
146
|
+
// Marketplace plugin
|
|
147
|
+
//
|
|
148
|
+
// Worth its own section because the two channels look identical from the
|
|
149
|
+
// outside and produce opposite expectations: with the plugin enabled, absent
|
|
150
|
+
// mirrors are correct; without it, absent mirrors mean no skills at all.
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log(' Marketplace plugin:');
|
|
153
|
+
const pluginEnabled = isClaudePluginEnabled(homeDir);
|
|
154
|
+
const pluginCached = hasClaudePluginCache(homeDir);
|
|
155
|
+
|
|
156
|
+
if (pluginEnabled) {
|
|
157
|
+
console.log(` ${CHECK} Enabled — Claude Code is served by the plugin, mirrors intentionally absent`);
|
|
158
|
+
} else if (pluginCached) {
|
|
159
|
+
console.log(` ${WARN} Not enabled, but a cache directory remains from a previous install`);
|
|
160
|
+
console.log(` ${chalk.dim('Harmless on this version. On aiskills < 1.16.1 it made install skip')}`);
|
|
161
|
+
console.log(` ${chalk.dim('every symlink, leaving Claude Code with no skills. Remove it with:')}`);
|
|
162
|
+
console.log(` ${chalk.cyan('rm -rf ~/.claude/plugins/cache/maccesar-aiskills')}`);
|
|
163
|
+
} else {
|
|
164
|
+
console.log(` ${CHECK} Not installed — skills reach Claude Code through npm mirrors`);
|
|
165
|
+
}
|
|
166
|
+
|
|
120
167
|
// Symlink issues detail
|
|
121
168
|
if (symlinkIssues.length > 0) {
|
|
122
169
|
console.log('');
|
package/lib/commands/skills.js
CHANGED
|
@@ -79,10 +79,12 @@ export async function skillsCommand(options) {
|
|
|
79
79
|
chalk.dim('(agentskills.io standard)')
|
|
80
80
|
);
|
|
81
81
|
console.log(
|
|
82
|
-
' ' + chalk.
|
|
82
|
+
' ' + chalk.green('✓'),
|
|
83
|
+
chalk.dim('Gemini, Codex, Cursor, Cline, Amp, GitHub Copilot +more read it directly —')
|
|
83
84
|
);
|
|
85
|
+
console.log(' ' + chalk.dim('nothing else to configure for them.'));
|
|
84
86
|
console.log(
|
|
85
|
-
' ' + chalk.dim('
|
|
87
|
+
' ' + chalk.dim('Claude Code needs symlink mirrors, created below.')
|
|
86
88
|
);
|
|
87
89
|
console.log('');
|
|
88
90
|
|
|
@@ -112,10 +114,19 @@ export async function skillsCommand(options) {
|
|
|
112
114
|
process.exit(1);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
// Show detected platforms
|
|
117
|
+
// Show detected platforms.
|
|
118
|
+
//
|
|
119
|
+
// Only assistants that need aiskills-managed mirrors appear here, so a bare
|
|
120
|
+
// "Claude Code detected" reads as if the other assistants were not found at
|
|
121
|
+
// all. They were never looked for: Gemini, Codex and the rest read
|
|
122
|
+
// ~/.agents/skills/ directly and are already served by the install itself.
|
|
116
123
|
if (detectedPlatforms.length > 0) {
|
|
117
124
|
for (const platform of detectedPlatforms) {
|
|
118
|
-
console.log(
|
|
125
|
+
console.log(
|
|
126
|
+
chalk.green('✓'),
|
|
127
|
+
`${platform.displayName} detected`,
|
|
128
|
+
chalk.dim('— needs mirrors, linked below')
|
|
129
|
+
);
|
|
119
130
|
}
|
|
120
131
|
console.log('');
|
|
121
132
|
} else if (isLocal) {
|
|
@@ -269,6 +280,10 @@ export async function skillsCommand(options) {
|
|
|
269
280
|
spinner.succeed(
|
|
270
281
|
`${commandsResult.installed.length} slash command${commandsResult.installed.length !== 1 ? 's' : ''} installed`
|
|
271
282
|
);
|
|
283
|
+
} else if (commandsResult.skipped.length > 0) {
|
|
284
|
+
spinner.info(
|
|
285
|
+
`${commandsResult.skipped.length} slash command${commandsResult.skipped.length !== 1 ? 's' : ''} already provided by the marketplace plugin`
|
|
286
|
+
);
|
|
272
287
|
} else {
|
|
273
288
|
spinner.info('No slash commands to install');
|
|
274
289
|
}
|
package/lib/commands/update.js
CHANGED
|
@@ -67,6 +67,10 @@ async function performUpdate(baseDir, repoDir, spinner) {
|
|
|
67
67
|
spinner.succeed(
|
|
68
68
|
`${commandsResult.installed.length} slash command${commandsResult.installed.length !== 1 ? 's' : ''} synced`
|
|
69
69
|
);
|
|
70
|
+
} else if (commandsResult.skipped.length > 0) {
|
|
71
|
+
spinner.info(
|
|
72
|
+
`${commandsResult.skipped.length} slash command${commandsResult.skipped.length !== 1 ? 's' : ''} already provided by the marketplace plugin`
|
|
73
|
+
);
|
|
70
74
|
} else {
|
|
71
75
|
spinner.info('No slash commands to sync');
|
|
72
76
|
}
|
package/lib/config.js
CHANGED
|
@@ -30,6 +30,7 @@ export const SKILLS = [
|
|
|
30
30
|
'audit-codebase',
|
|
31
31
|
'humaniza',
|
|
32
32
|
'refactoring-ui',
|
|
33
|
+
'session-log',
|
|
33
34
|
'stitch-showcase',
|
|
34
35
|
'vscode-extension-dev',
|
|
35
36
|
];
|
|
@@ -63,6 +64,17 @@ export const CLAUDE_PLUGIN_NAME = 'aiskills';
|
|
|
63
64
|
export const getClaudePluginSkillsPath = (baseDir = os.homedir()) =>
|
|
64
65
|
path.join(baseDir, '.claude', 'plugins', 'cache', CLAUDE_PLUGIN_MARKETPLACE, CLAUDE_PLUGIN_NAME);
|
|
65
66
|
|
|
67
|
+
// The key Claude Code writes under "enabledPlugins" when the plugin is installed.
|
|
68
|
+
export const CLAUDE_PLUGIN_KEY = `${CLAUDE_PLUGIN_NAME}@${CLAUDE_PLUGIN_MARKETPLACE}`;
|
|
69
|
+
|
|
70
|
+
// Where Claude Code records which plugins are enabled. Both files are consulted
|
|
71
|
+
// because the local variant overrides the shared one, and either may carry the
|
|
72
|
+
// entry depending on how the plugin was installed.
|
|
73
|
+
export const getClaudeSettingsPaths = (baseDir = os.homedir()) => [
|
|
74
|
+
path.join(baseDir, '.claude', 'settings.json'),
|
|
75
|
+
path.join(baseDir, '.claude', 'settings.local.json'),
|
|
76
|
+
];
|
|
77
|
+
|
|
66
78
|
// AI platform detection
|
|
67
79
|
//
|
|
68
80
|
// Only platforms that need aiskills-managed symlinks appear here.
|
package/lib/installer.js
CHANGED
|
@@ -135,6 +135,7 @@ export async function installCommands(repoDir, baseDir = os.homedir()) {
|
|
|
135
135
|
installed: [],
|
|
136
136
|
failed: [],
|
|
137
137
|
removed: [],
|
|
138
|
+
skipped: [],
|
|
138
139
|
};
|
|
139
140
|
|
|
140
141
|
const legacyLocal = removeCommands(baseDir, { legacyOnly: true });
|
|
@@ -147,7 +148,24 @@ export async function installCommands(repoDir, baseDir = os.homedir()) {
|
|
|
147
148
|
results.failed.push(...legacyGlobal.failed);
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
const { pluginProvidesCommand } = await import('./claude-plugin.js');
|
|
152
|
+
const commandsDir = getClaudeCommandsDir(baseDir);
|
|
153
|
+
|
|
150
154
|
for (const cmd of COMMANDS) {
|
|
155
|
+
// Same rule the symlink step applies to skills: when the marketplace plugin
|
|
156
|
+
// already provides the command, installing our own copy makes it show up
|
|
157
|
+
// twice in the autocomplete. Clean up any copy left from before the plugin
|
|
158
|
+
// was installed.
|
|
159
|
+
if (pluginProvidesCommand(cmd, baseDir)) {
|
|
160
|
+
const stalePath = join(commandsDir, `${cmd}.md`);
|
|
161
|
+
if (existsSync(stalePath)) {
|
|
162
|
+
await remove(stalePath);
|
|
163
|
+
results.removed.push(cmd);
|
|
164
|
+
}
|
|
165
|
+
results.skipped.push(cmd);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
151
169
|
if (await installCommand(repoDir, cmd, baseDir)) {
|
|
152
170
|
results.installed.push(cmd);
|
|
153
171
|
} else {
|
package/lib/symlink.js
CHANGED
|
@@ -75,21 +75,16 @@ async function removePath(path) {
|
|
|
75
75
|
* and an additional symlink at ~/.claude/skills/<skill> produces a duplicate
|
|
76
76
|
* entry in the slash-command autocomplete. This helper lets the symlink step
|
|
77
77
|
* skip Claude when the plugin already covers it.
|
|
78
|
+
*
|
|
79
|
+
* Requires the plugin to be *enabled*, not merely cached — see `claude-plugin.js`
|
|
80
|
+
* for why the distinction matters.
|
|
78
81
|
* @param {string} skillName - Skill name (e.g. 'stitch-showcase')
|
|
79
82
|
* @param {string} baseDir - Optional base directory (defaults to homedir via config)
|
|
80
83
|
* @returns {Promise<boolean>} True if the plugin provides this skill
|
|
81
84
|
*/
|
|
82
85
|
export async function isClaudePluginSkillInstalled(skillName, baseDir) {
|
|
83
|
-
const {
|
|
84
|
-
|
|
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
|
-
}
|
|
86
|
+
const { pluginProvidesSkill } = await import('./claude-plugin.js');
|
|
87
|
+
return pluginProvidesSkill(skillName, baseDir);
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
/**
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maccesar/aiskills",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
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
|
},
|