@maccesar/aiskills 1.19.0 → 1.20.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 CHANGED
@@ -540,10 +540,11 @@ How to invoke it — in whatever words you'd use anyway:
540
540
 
541
541
  ### aiskills list
542
542
 
543
- Lists all available skills with their descriptions.
543
+ Lists all available skills with their descriptions, marking each one installed (✓) or not (✗). It works before you have installed anything — descriptions for a skill that is not installed come from the copy bundled in this package — so the list doubles as the catalog of what is on offer.
544
544
 
545
545
  ```bash
546
546
  aiskills list
547
+ aiskills ls # same thing
547
548
  ```
548
549
 
549
550
  ---
package/bin/aiskills.js CHANGED
@@ -25,6 +25,7 @@ program
25
25
  // List command
26
26
  program
27
27
  .command('list')
28
+ .alias('ls')
28
29
  .description('List available AI skills')
29
30
  .action(listCommand);
30
31
 
@@ -111,25 +111,44 @@ export function parseDescription(frontmatter) {
111
111
  }
112
112
 
113
113
  /**
114
- * Read a skill's description from disk.
115
- * @param {string} skillDir - Directory holding the skill's SKILL.md
116
- * @returns {{description: string|null, installed: boolean}}
114
+ * Read a description out of one SKILL.md.
115
+ * @param {string} skillMd - Path to a SKILL.md
116
+ * @returns {string|null} The first sentence, or null when unreadable
117
117
  */
118
- function readSkillMetadata(skillDir) {
119
- const skillMd = join(skillDir, 'SKILL.md');
120
- if (!existsSync(skillMd)) {
121
- return { description: null, installed: false };
122
- }
123
-
118
+ function readDescription(skillMd) {
124
119
  try {
125
120
  const content = readFileSync(skillMd, 'utf8');
126
121
  const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
127
- if (!frontmatter) return { description: null, installed: true };
128
-
129
- return { description: parseDescription(frontmatter[1]), installed: true };
122
+ if (!frontmatter) return null;
123
+ return parseDescription(frontmatter[1]);
130
124
  } catch {
131
- return { description: null, installed: true };
125
+ return null;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Read a skill's description, preferring the installed copy and falling back
131
+ * to the one bundled in this package.
132
+ *
133
+ * The fallback is what lets the list describe a skill that is not installed
134
+ * yet — which is exactly when someone runs `aiskills list` and needs to know
135
+ * what they would be installing. `skills/` ships in the npm tarball, so the
136
+ * bundled copy is there for every install channel.
137
+ * @param {string} skillDir - Where the skill would be installed
138
+ * @param {string} name - Skill name, used to find the bundled copy
139
+ * @returns {{description: string|null, installed: boolean}}
140
+ */
141
+ function readSkillMetadata(skillDir, name) {
142
+ const installedMd = join(skillDir, 'SKILL.md');
143
+ if (existsSync(installedMd)) {
144
+ return { description: readDescription(installedMd), installed: true };
132
145
  }
146
+
147
+ const bundledMd = new URL(`../../skills/${name}/SKILL.md`, import.meta.url);
148
+ return {
149
+ description: existsSync(bundledMd) ? readDescription(bundledMd) : null,
150
+ installed: false,
151
+ };
133
152
  }
134
153
 
135
154
  export async function listCommand() {
@@ -139,21 +158,13 @@ export async function listCommand() {
139
158
 
140
159
  const skillsDir = getAgentsSkillsDir();
141
160
 
142
- if (!existsSync(skillsDir)) {
143
- console.log(chalk.yellow('No skills installed yet.'));
144
- console.log('Install with:');
145
- console.log(chalk.cyan(' aiskills install'));
146
- console.log('');
147
- return;
148
- }
149
-
150
161
  const rows = [];
151
162
  let installedCount = 0;
152
163
  let maxNameLen = 0;
153
164
 
154
165
  for (const name of SKILLS) {
155
166
  const skillDir = join(skillsDir, name);
156
- const { description, installed } = readSkillMetadata(skillDir);
167
+ const { description, installed } = readSkillMetadata(skillDir, name);
157
168
 
158
169
  if (installed) installedCount++;
159
170
  if (name.length > maxNameLen) maxNameLen = name.length;
@@ -179,6 +190,18 @@ export async function listCommand() {
179
190
  }
180
191
 
181
192
  console.log('');
193
+
194
+ if (installedCount === 0) {
195
+ // Naming the directory would point at somewhere that does not exist yet.
196
+ console.log(chalk.gray(`0/${SKILLS.length} installed`));
197
+ console.log('');
198
+ console.log(chalk.yellow('No skills installed yet.'));
199
+ console.log('Install with:');
200
+ console.log(chalk.cyan(' aiskills install'));
201
+ console.log('');
202
+ return;
203
+ }
204
+
182
205
  console.log(chalk.gray(`${installedCount}/${SKILLS.length} installed at ${skillsDir}`));
183
206
  console.log('');
184
207
  console.log(chalk.gray('Run `aiskills status` for installation health.'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maccesar/aiskills",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "AI coding assistant skills for Claude Code, Gemini CLI, and Codex CLI",
5
5
  "type": "module",
6
6
  "bin": {