incur 0.3.12 → 0.3.14
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/dist/Cli.d.ts +2 -0
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +27 -6
- package/dist/Cli.js.map +1 -1
- package/dist/Skill.d.ts +2 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +29 -15
- package/dist/Skill.js.map +1 -1
- package/dist/Skillgen.js +1 -1
- package/dist/Skillgen.js.map +1 -1
- package/dist/SyncSkills.d.ts +10 -0
- package/dist/SyncSkills.d.ts.map +1 -1
- package/dist/SyncSkills.js +23 -4
- package/dist/SyncSkills.js.map +1 -1
- package/package.json +1 -27
- package/src/Cli.test-d.ts +9 -0
- package/src/Cli.test.ts +43 -0
- package/src/Cli.ts +23 -5
- package/src/Skill.test.ts +64 -0
- package/src/Skill.ts +32 -16
- package/src/Skillgen.ts +1 -1
- package/src/SyncSkills.ts +38 -3
package/src/Skill.ts
CHANGED
|
@@ -5,7 +5,8 @@ import * as Schema from './Schema.js'
|
|
|
5
5
|
|
|
6
6
|
/** Information about a single command, passed to `generate()`. */
|
|
7
7
|
export type CommandInfo = {
|
|
8
|
-
name
|
|
8
|
+
/** Command name (subcommand path). Omit for root commands. */
|
|
9
|
+
name?: string | undefined
|
|
9
10
|
description?: string | undefined
|
|
10
11
|
args?: z.ZodObject<any> | undefined
|
|
11
12
|
env?: z.ZodObject<any> | undefined
|
|
@@ -48,7 +49,7 @@ export function index(
|
|
|
48
49
|
|
|
49
50
|
/** @internal Builds a command signature with arg placeholders. */
|
|
50
51
|
function buildSignature(cli: string, cmd: CommandInfo): string {
|
|
51
|
-
const base = `${cli} ${cmd.name}`
|
|
52
|
+
const base = !cmd.name ? cli : `${cli} ${cmd.name}`
|
|
52
53
|
if (!cmd.args) return base
|
|
53
54
|
const shape = cmd.args.shape as Record<string, z.ZodType>
|
|
54
55
|
const json = Schema.toJsonSchema(cmd.args)
|
|
@@ -70,14 +71,16 @@ export function generate(
|
|
|
70
71
|
let lastGroup: string | undefined
|
|
71
72
|
|
|
72
73
|
for (const cmd of commands) {
|
|
73
|
-
const segment = cmd.name.split(' ')[0]!
|
|
74
|
+
const segment = !cmd.name ? '' : cmd.name.split(' ')[0]!
|
|
74
75
|
if (segment !== lastGroup) {
|
|
75
76
|
lastGroup = segment
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
if (segment) {
|
|
78
|
+
const desc = groups.get(segment)
|
|
79
|
+
const heading = desc ? `## ${name} ${segment}\n\n${desc}` : `## ${name} ${segment}`
|
|
80
|
+
sections.push(heading)
|
|
81
|
+
}
|
|
79
82
|
}
|
|
80
|
-
sections.push(renderCommandBody(name, cmd, 3))
|
|
83
|
+
sections.push(renderCommandBody(name, cmd, segment ? 3 : 2))
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
return sections.join('\n\n')
|
|
@@ -94,6 +97,13 @@ export function split(
|
|
|
94
97
|
|
|
95
98
|
const buckets = new Map<string, CommandInfo[]>()
|
|
96
99
|
for (const cmd of commands) {
|
|
100
|
+
if (!cmd.name) {
|
|
101
|
+
const key = slugify(name)
|
|
102
|
+
const bucket = buckets.get(key) ?? []
|
|
103
|
+
bucket.push(cmd)
|
|
104
|
+
buckets.set(key, bucket)
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
97
107
|
const segments = cmd.name.split(' ')
|
|
98
108
|
const key = segments.slice(0, depth).join('-')
|
|
99
109
|
const bucket = buckets.get(key) ?? []
|
|
@@ -104,8 +114,10 @@ export function split(
|
|
|
104
114
|
return [...buckets.entries()]
|
|
105
115
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
106
116
|
.map(([dir, cmds]) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
117
|
+
const first = cmds[0]!
|
|
118
|
+
const prefix = !first.name ? '' : first.name.split(' ').slice(0, depth).join(' ')
|
|
119
|
+
const title = prefix ? `${name} ${prefix}` : name
|
|
120
|
+
return { dir, content: renderGroup(name, title, cmds, groups, prefix || undefined) }
|
|
109
121
|
})
|
|
110
122
|
}
|
|
111
123
|
|
|
@@ -127,12 +139,7 @@ function renderGroup(
|
|
|
127
139
|
? `${descParts.join('. ')}. Run \`${title} --help\` for usage details.`
|
|
128
140
|
: `Run \`${title} --help\` for usage details.`
|
|
129
141
|
|
|
130
|
-
const
|
|
131
|
-
.toLowerCase()
|
|
132
|
-
.replace(/[^a-z0-9-]+/g, '-')
|
|
133
|
-
.replace(/-{2,}/g, '-')
|
|
134
|
-
.replace(/^-|-$/g, '')
|
|
135
|
-
const fm = ['---', `name: ${slug}`]
|
|
142
|
+
const fm = ['---', `name: ${slugify(title)}`]
|
|
136
143
|
fm.push(`description: ${description}`)
|
|
137
144
|
fm.push(`requires_bin: ${cli}`)
|
|
138
145
|
fm.push(`command: ${title}`, '---')
|
|
@@ -143,7 +150,7 @@ function renderGroup(
|
|
|
143
150
|
|
|
144
151
|
/** @internal Renders a command's heading and sections without frontmatter. */
|
|
145
152
|
function renderCommandBody(cli: string, cmd: CommandInfo, level = 1): string {
|
|
146
|
-
const fullName = `${cli} ${cmd.name}`
|
|
153
|
+
const fullName = !cmd.name ? cli : `${cli} ${cmd.name}`
|
|
147
154
|
const sections: string[] = []
|
|
148
155
|
const h = (n: number) => '#'.repeat(n)
|
|
149
156
|
|
|
@@ -287,6 +294,15 @@ function schemaToTable(schema: Record<string, unknown>, prefix = ''): string | u
|
|
|
287
294
|
return `| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n${rows.join('\n')}`
|
|
288
295
|
}
|
|
289
296
|
|
|
297
|
+
/** @internal Converts a string to a lowercase slug (e.g. `"my-cli"` → `"my-cli"`, `"My Tool"` → `"my-tool"`). */
|
|
298
|
+
function slugify(s: string): string {
|
|
299
|
+
return s
|
|
300
|
+
.toLowerCase()
|
|
301
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
302
|
+
.replace(/-{2,}/g, '-')
|
|
303
|
+
.replace(/^-|-$/g, '')
|
|
304
|
+
}
|
|
305
|
+
|
|
290
306
|
/** @internal Resolves a simple type name from a JSON Schema property. */
|
|
291
307
|
function resolveTypeName(prop: Record<string, unknown> | undefined): string {
|
|
292
308
|
if (!prop) return 'unknown'
|
package/src/Skillgen.ts
CHANGED
package/src/SyncSkills.ts
CHANGED
|
@@ -18,7 +18,7 @@ export async function sync(
|
|
|
18
18
|
|
|
19
19
|
const groups = new Map<string, string>()
|
|
20
20
|
if (description) groups.set(name, description)
|
|
21
|
-
const entries = collectEntries(commands, [], groups)
|
|
21
|
+
const entries = collectEntries(commands, [], groups, options.rootCommand)
|
|
22
22
|
const files = Skill.split(name, entries, depth, groups)
|
|
23
23
|
|
|
24
24
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), `incur-skills-${name}-`))
|
|
@@ -70,7 +70,7 @@ export async function sync(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Write skills hash + names for staleness detection
|
|
73
|
-
const hashEntries = collectEntries(commands, [])
|
|
73
|
+
const hashEntries = collectEntries(commands, [], undefined, options.rootCommand)
|
|
74
74
|
writeMeta(name, Skill.hash(hashEntries), [...currentNames])
|
|
75
75
|
|
|
76
76
|
return { skills, paths, agents }
|
|
@@ -92,6 +92,18 @@ export declare namespace sync {
|
|
|
92
92
|
global?: boolean | undefined
|
|
93
93
|
/** Glob patterns for directories containing SKILL.md files to include (e.g. `"skills/*"`, `"my-skill"`). Skill name is the parent directory name. */
|
|
94
94
|
include?: string[] | undefined
|
|
95
|
+
/** Root command definition (when the CLI itself has a `run` handler). */
|
|
96
|
+
rootCommand?:
|
|
97
|
+
| {
|
|
98
|
+
description?: string | undefined
|
|
99
|
+
args?: any
|
|
100
|
+
env?: any
|
|
101
|
+
hint?: string | undefined
|
|
102
|
+
options?: any
|
|
103
|
+
output?: any
|
|
104
|
+
examples?: any[] | undefined
|
|
105
|
+
}
|
|
106
|
+
| undefined
|
|
95
107
|
}
|
|
96
108
|
/** Result of a sync operation. */
|
|
97
109
|
type Result = {
|
|
@@ -118,8 +130,31 @@ function collectEntries(
|
|
|
118
130
|
commands: Map<string, any>,
|
|
119
131
|
prefix: string[],
|
|
120
132
|
groups: Map<string, string> = new Map(),
|
|
133
|
+
rootCommand?:
|
|
134
|
+
| {
|
|
135
|
+
description?: string | undefined
|
|
136
|
+
args?: any
|
|
137
|
+
env?: any
|
|
138
|
+
hint?: string | undefined
|
|
139
|
+
options?: any
|
|
140
|
+
output?: any
|
|
141
|
+
examples?: any[] | undefined
|
|
142
|
+
}
|
|
143
|
+
| undefined,
|
|
121
144
|
): Skill.CommandInfo[] {
|
|
122
145
|
const result: Skill.CommandInfo[] = []
|
|
146
|
+
if (rootCommand) {
|
|
147
|
+
const cmd: Skill.CommandInfo = {}
|
|
148
|
+
if (rootCommand.description) cmd.description = rootCommand.description
|
|
149
|
+
if (rootCommand.args) cmd.args = rootCommand.args
|
|
150
|
+
if (rootCommand.env) cmd.env = rootCommand.env
|
|
151
|
+
if (rootCommand.hint) cmd.hint = rootCommand.hint
|
|
152
|
+
if (rootCommand.options) cmd.options = rootCommand.options
|
|
153
|
+
if (rootCommand.output) cmd.output = rootCommand.output
|
|
154
|
+
const examples = formatExamples(rootCommand.examples)
|
|
155
|
+
if (examples) cmd.examples = examples
|
|
156
|
+
result.push(cmd)
|
|
157
|
+
}
|
|
123
158
|
for (const [name, entry] of commands) {
|
|
124
159
|
const entryPath = [...prefix, name]
|
|
125
160
|
if ('_group' in entry && entry._group) {
|
|
@@ -144,7 +179,7 @@ function collectEntries(
|
|
|
144
179
|
result.push(cmd)
|
|
145
180
|
}
|
|
146
181
|
}
|
|
147
|
-
return result.sort((a, b) => a.name.localeCompare(b.name))
|
|
182
|
+
return result.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''))
|
|
148
183
|
}
|
|
149
184
|
|
|
150
185
|
/** 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()`. */
|