incur 0.4.2 → 0.4.4

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/src/SyncSkills.ts CHANGED
@@ -2,6 +2,7 @@ import fsSync from 'node:fs'
2
2
  import fs from 'node:fs/promises'
3
3
  import os from 'node:os'
4
4
  import path from 'node:path'
5
+ import { parse as yamlParse } from 'yaml'
5
6
 
6
7
  import { formatExamples } from './Cli.js'
7
8
  import * as Agents from './internal/agents.js'
@@ -30,9 +31,8 @@ export async function sync(
30
31
  : path.join(tmpDir, 'SKILL.md')
31
32
  await fs.mkdir(path.dirname(filePath), { recursive: true })
32
33
  await fs.writeFile(filePath, `${file.content}\n`)
33
- const nameMatch = file.content.match(/^name:\s*(.+)$/m)
34
- const descMatch = file.content.match(/^description:\s*(.+)$/m)
35
- skills.push({ name: nameMatch?.[1] ?? (file.dir || name), description: descMatch?.[1] })
34
+ const meta = parseFrontmatter(file.content)
35
+ skills.push({ name: meta.name ?? (file.dir || name), description: meta.description })
36
36
  }
37
37
 
38
38
  // Include additional SKILL.md files matched by glob patterns
@@ -42,16 +42,14 @@ export async function sync(
42
42
  for await (const match of fs.glob(globPattern, { cwd })) {
43
43
  try {
44
44
  const content = await fs.readFile(path.resolve(cwd, match), 'utf8')
45
- const nameMatch = content.match(/^name:\s*(.+)$/m)
45
+ const meta = parseFrontmatter(content)
46
46
  const skillName =
47
- pattern === '_root' ? (nameMatch?.[1] ?? name) : path.basename(path.dirname(match))
47
+ pattern === '_root' ? (meta.name ?? name) : path.basename(path.dirname(match))
48
48
  const dest = path.join(tmpDir, skillName, 'SKILL.md')
49
49
  await fs.mkdir(path.dirname(dest), { recursive: true })
50
50
  await fs.writeFile(dest, content)
51
- if (!skills.some((s) => s.name === skillName)) {
52
- const descMatch = content.match(/^description:\s*(.+)$/m)
53
- skills.push({ name: skillName, description: descMatch?.[1], external: true })
54
- }
51
+ if (!skills.some((s) => s.name === skillName))
52
+ skills.push({ name: skillName, description: meta.description, external: true })
55
53
  } catch {}
56
54
  }
57
55
  }
@@ -148,12 +146,11 @@ export async function list(
148
146
  const installed = readInstalledSkills(name, { cwd })
149
147
 
150
148
  for (const file of files) {
151
- const nameMatch = file.content.match(/^name:\s*(.+)$/m)
152
- const descMatch = file.content.match(/^description:\s*(.+)$/m)
153
- const skillName = nameMatch?.[1] ?? (file.dir || name)
149
+ const meta = parseFrontmatter(file.content)
150
+ const skillName = meta.name ?? (file.dir || name)
154
151
  skills.push({
155
152
  name: skillName,
156
- description: descMatch?.[1],
153
+ description: meta.description,
157
154
  installed: installed.has(skillName),
158
155
  })
159
156
  }
@@ -165,14 +162,12 @@ export async function list(
165
162
  for await (const match of fs.glob(globPattern, { cwd })) {
166
163
  try {
167
164
  const content = await fs.readFile(path.resolve(cwd, match), 'utf8')
168
- const nameMatch = content.match(/^name:\s*(.+)$/m)
169
- const skillName =
170
- pattern === '_root' ? (nameMatch?.[1] ?? name) : path.basename(path.dirname(match))
165
+ const meta = parseFrontmatter(content)
166
+ const skillName = pattern === '_root' ? (meta.name ?? name) : path.basename(path.dirname(match))
171
167
  if (!skills.some((s) => s.name === skillName)) {
172
- const descMatch = content.match(/^description:\s*(.+)$/m)
173
168
  skills.push({
174
169
  name: skillName,
175
- description: descMatch?.[1],
170
+ description: meta.description,
176
171
  installed: installed.has(skillName),
177
172
  })
178
173
  }
@@ -284,6 +279,15 @@ function collectEntries(
284
279
  return result.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''))
285
280
  }
286
281
 
282
+ function parseFrontmatter(content: string): { description?: string | undefined; name?: string | undefined } {
283
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
284
+ if (!match) return {}
285
+
286
+ const meta = yamlParse(match[1]!)
287
+ if (!meta || typeof meta !== 'object') return {}
288
+ return meta as { description?: string | undefined; name?: string | undefined }
289
+ }
290
+
287
291
  /** Resolves the package root from the executing bin script (`process.argv[1]`). Walks up from the bin's directory looking for `package.json`. Falls back to `process.cwd()`. */
288
292
  function resolvePackageRoot(): string {
289
293
  const bin = process.argv[1]
@@ -350,9 +350,16 @@ export type CommandMeta<options extends z.ZodObject<any> | undefined = undefined
350
350
  options?: options | undefined
351
351
  }
352
352
 
353
+ /** @internal Metadata for a built-in subcommand. */
354
+ type BuiltinSubcommandMeta<options extends z.ZodObject<any> | undefined = undefined> =
355
+ CommandMeta<options> & {
356
+ /** Alternative names for this built-in subcommand. */
357
+ aliases?: string[] | undefined
358
+ }
359
+
353
360
  /** @internal Creates a builtin subcommand with typesafe alias inference. */
354
361
  function subcommand<const options extends z.ZodObject<any> | undefined = undefined>(
355
- def: CommandMeta<options> & { name: string },
362
+ def: BuiltinSubcommandMeta<options> & { name: string },
356
363
  ) {
357
364
  return def
358
365
  }
@@ -425,6 +432,7 @@ export const builtinCommands = [
425
432
  }),
426
433
  subcommand({
427
434
  name: 'list',
435
+ aliases: ['ls'],
428
436
  description: 'List skills',
429
437
  }),
430
438
  ],
@@ -435,7 +443,7 @@ export const builtinCommands = [
435
443
  args?: z.ZodObject<any> | undefined
436
444
  description: string
437
445
  hint?: ((name: string) => string) | undefined
438
- subcommands?: (CommandMeta<z.ZodObject<any>> & { name: string })[] | undefined
446
+ subcommands?: (BuiltinSubcommandMeta<z.ZodObject<any>> & { name: string })[] | undefined
439
447
  }[]
440
448
 
441
449
  /** @internal Finds a builtin command by its name or alias. */
@@ -443,6 +451,11 @@ export function findBuiltin(token: string) {
443
451
  return builtinCommands.find((b) => b.name === token || b.aliases?.includes(token))
444
452
  }
445
453
 
454
+ /** @internal Finds a builtin subcommand by its name or alias. */
455
+ export function findBuiltinSubcommand(builtin: (typeof builtinCommands)[number], token: string) {
456
+ return builtin.subcommands?.find((sub) => sub.name === token || sub.aliases?.includes(token))
457
+ }
458
+
446
459
  /** @internal Checks if a token matches a builtin command by name or alias. */
447
460
  export function isBuiltin(token: string) {
448
461
  return builtinCommands.some((b) => b.name === token || b.aliases?.includes(token))