incur 0.0.0 → 0.0.2
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/LICENSE +21 -0
- package/README.md +141 -0
- package/SKILL.md +664 -0
- package/dist/Cli.d.ts +255 -0
- package/dist/Cli.d.ts.map +1 -0
- package/dist/Cli.js +900 -0
- package/dist/Cli.js.map +1 -0
- package/dist/Errors.d.ts +92 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +75 -0
- package/dist/Errors.js.map +1 -0
- package/dist/Formatter.d.ts +5 -0
- package/dist/Formatter.d.ts.map +1 -0
- package/dist/Formatter.js +91 -0
- package/dist/Formatter.js.map +1 -0
- package/dist/Help.d.ts +53 -0
- package/dist/Help.d.ts.map +1 -0
- package/dist/Help.js +231 -0
- package/dist/Help.js.map +1 -0
- package/dist/Mcp.d.ts +13 -0
- package/dist/Mcp.d.ts.map +1 -0
- package/dist/Mcp.js +140 -0
- package/dist/Mcp.js.map +1 -0
- package/dist/Parser.d.ts +24 -0
- package/dist/Parser.d.ts.map +1 -0
- package/dist/Parser.js +215 -0
- package/dist/Parser.js.map +1 -0
- package/dist/Register.d.ts +19 -0
- package/dist/Register.d.ts.map +1 -0
- package/dist/Register.js +2 -0
- package/dist/Register.js.map +1 -0
- package/dist/Schema.d.ts +4 -0
- package/dist/Schema.d.ts.map +1 -0
- package/dist/Schema.js +8 -0
- package/dist/Schema.js.map +1 -0
- package/dist/Skill.d.ts +29 -0
- package/dist/Skill.d.ts.map +1 -0
- package/dist/Skill.js +196 -0
- package/dist/Skill.js.map +1 -0
- package/dist/Skillgen.d.ts +3 -0
- package/dist/Skillgen.d.ts.map +1 -0
- package/dist/Skillgen.js +67 -0
- package/dist/Skillgen.js.map +1 -0
- package/dist/SyncMcp.d.ts +23 -0
- package/dist/SyncMcp.d.ts.map +1 -0
- package/dist/SyncMcp.js +100 -0
- package/dist/SyncMcp.js.map +1 -0
- package/dist/SyncSkills.d.ts +38 -0
- package/dist/SyncSkills.d.ts.map +1 -0
- package/dist/SyncSkills.js +163 -0
- package/dist/SyncSkills.js.map +1 -0
- package/dist/Typegen.d.ts +6 -0
- package/dist/Typegen.d.ts.map +1 -0
- package/dist/Typegen.js +92 -0
- package/dist/Typegen.js.map +1 -0
- package/dist/bin.d.ts +14 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +30 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/pm.d.ts +3 -0
- package/dist/internal/pm.d.ts.map +1 -0
- package/dist/internal/pm.js +11 -0
- package/dist/internal/pm.js.map +1 -0
- package/dist/internal/types.d.ts +11 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/internal/utils.d.ts +8 -0
- package/dist/internal/utils.d.ts.map +1 -0
- package/dist/internal/utils.js +51 -0
- package/dist/internal/utils.js.map +1 -0
- package/examples/npm/cli.ts +180 -0
- package/examples/npm/node_modules/.bin/incur.src +21 -0
- package/examples/npm/node_modules/.bin/tsx +21 -0
- package/examples/npm/package.json +14 -0
- package/examples/npm/tsconfig.json +9 -0
- package/examples/presto/cli.ts +246 -0
- package/examples/presto/node_modules/.bin/incur.src +21 -0
- package/examples/presto/node_modules/.bin/tsx +21 -0
- package/examples/presto/package.json +14 -0
- package/examples/presto/tsconfig.json +9 -0
- package/package.json +53 -2
- package/src/Cli.test-d.ts +135 -0
- package/src/Cli.test.ts +1373 -0
- package/src/Cli.ts +1470 -0
- package/src/Errors.test.ts +96 -0
- package/src/Errors.ts +139 -0
- package/src/Formatter.test.ts +245 -0
- package/src/Formatter.ts +106 -0
- package/src/Help.test.ts +124 -0
- package/src/Help.ts +302 -0
- package/src/Mcp.test.ts +254 -0
- package/src/Mcp.ts +195 -0
- package/src/Parser.test-d.ts +45 -0
- package/src/Parser.test.ts +118 -0
- package/src/Parser.ts +247 -0
- package/src/Register.ts +18 -0
- package/src/Schema.test.ts +125 -0
- package/src/Schema.ts +8 -0
- package/src/Skill.test.ts +293 -0
- package/src/Skill.ts +253 -0
- package/src/Skillgen.ts +66 -0
- package/src/SyncMcp.test.ts +75 -0
- package/src/SyncMcp.ts +132 -0
- package/src/SyncSkills.test.ts +92 -0
- package/src/SyncSkills.ts +205 -0
- package/src/Typegen.test.ts +150 -0
- package/src/Typegen.ts +107 -0
- package/src/bin.ts +33 -0
- package/src/e2e.test.ts +1710 -0
- package/src/index.ts +14 -0
- package/src/internal/pm.test.ts +38 -0
- package/src/internal/pm.ts +8 -0
- package/src/internal/types.ts +22 -0
- package/src/internal/utils.ts +50 -0
- package/src/tsconfig.json +8 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Cli, Typegen, z } from 'incur'
|
|
2
|
+
|
|
3
|
+
describe('fromCli', () => {
|
|
4
|
+
test('simple commands with args and options', () => {
|
|
5
|
+
const cli = Cli.create('test')
|
|
6
|
+
.command('get', {
|
|
7
|
+
args: z.object({ id: z.number() }),
|
|
8
|
+
run: () => ({}),
|
|
9
|
+
})
|
|
10
|
+
.command('list', {
|
|
11
|
+
options: z.object({ limit: z.number() }),
|
|
12
|
+
run: () => ({}),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
expect(Typegen.fromCli(cli)).toMatchInlineSnapshot(`
|
|
16
|
+
"declare module 'incur' {
|
|
17
|
+
interface Register {
|
|
18
|
+
commands: {
|
|
19
|
+
'get': { args: { id: number }; options: {} }
|
|
20
|
+
'list': { args: {}; options: { limit: number } }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
"
|
|
25
|
+
`)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('command with no args or options', () => {
|
|
29
|
+
const cli = Cli.create('test').command('ping', { run: () => ({}) })
|
|
30
|
+
|
|
31
|
+
expect(Typegen.fromCli(cli)).toMatchInlineSnapshot(`
|
|
32
|
+
"declare module 'incur' {
|
|
33
|
+
interface Register {
|
|
34
|
+
commands: {
|
|
35
|
+
'ping': { args: {}; options: {} }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
"
|
|
40
|
+
`)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('sub-commands use full path', () => {
|
|
44
|
+
const cli = Cli.create('test')
|
|
45
|
+
const pr = Cli.create('pr')
|
|
46
|
+
.command('list', {
|
|
47
|
+
options: z.object({ state: z.string() }),
|
|
48
|
+
run: () => ({}),
|
|
49
|
+
})
|
|
50
|
+
.command('create', {
|
|
51
|
+
args: z.object({ title: z.string() }),
|
|
52
|
+
run: () => ({}),
|
|
53
|
+
})
|
|
54
|
+
cli.command(pr)
|
|
55
|
+
|
|
56
|
+
expect(Typegen.fromCli(cli)).toMatchInlineSnapshot(`
|
|
57
|
+
"declare module 'incur' {
|
|
58
|
+
interface Register {
|
|
59
|
+
commands: {
|
|
60
|
+
'pr create': { args: { title: string }; options: {} }
|
|
61
|
+
'pr list': { args: {}; options: { state: string } }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
"
|
|
66
|
+
`)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('deeply nested sub-commands', () => {
|
|
70
|
+
const cli = Cli.create('test')
|
|
71
|
+
const review = Cli.create('review').command('approve', {
|
|
72
|
+
args: z.object({ id: z.number() }),
|
|
73
|
+
run: () => ({}),
|
|
74
|
+
})
|
|
75
|
+
const pr = Cli.create('pr')
|
|
76
|
+
pr.command(review)
|
|
77
|
+
cli.command(pr)
|
|
78
|
+
|
|
79
|
+
expect(Typegen.fromCli(cli)).toMatchInlineSnapshot(`
|
|
80
|
+
"declare module 'incur' {
|
|
81
|
+
interface Register {
|
|
82
|
+
commands: {
|
|
83
|
+
'pr review approve': { args: { id: number }; options: {} }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
"
|
|
88
|
+
`)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('enum types fromCli union of literals', () => {
|
|
92
|
+
const cli = Cli.create('test').command('list', {
|
|
93
|
+
options: z.object({ state: z.enum(['open', 'closed', 'merged']) }),
|
|
94
|
+
run: () => ({}),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const output = Typegen.fromCli(cli)
|
|
98
|
+
expect(output).toContain('"open" | "closed" | "merged"')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('boolean types', () => {
|
|
102
|
+
const cli = Cli.create('test').command('list', {
|
|
103
|
+
options: z.object({ verbose: z.boolean() }),
|
|
104
|
+
run: () => ({}),
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const output = Typegen.fromCli(cli)
|
|
108
|
+
expect(output).toContain('verbose: boolean')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('array types', () => {
|
|
112
|
+
const cli = Cli.create('test').command('list', {
|
|
113
|
+
options: z.object({ tags: z.array(z.string()) }),
|
|
114
|
+
run: () => ({}),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const output = Typegen.fromCli(cli)
|
|
118
|
+
expect(output).toContain('tags: string[]')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('commands are sorted alphabetically', () => {
|
|
122
|
+
const cli = Cli.create('test')
|
|
123
|
+
.command('zebra', { run: () => ({}) })
|
|
124
|
+
.command('alpha', { run: () => ({}) })
|
|
125
|
+
.command('middle', { run: () => ({}) })
|
|
126
|
+
|
|
127
|
+
const output = Typegen.fromCli(cli)
|
|
128
|
+
const commandOrder = [...output.matchAll(/^ {6}'(\w+)':/gm)].map((m) => m[1])
|
|
129
|
+
expect(commandOrder).toEqual(['alpha', 'middle', 'zebra'])
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('mixed top-level and grouped commands', () => {
|
|
133
|
+
const cli = Cli.create('test')
|
|
134
|
+
cli.command('ping', { run: () => ({}) })
|
|
135
|
+
const pr = Cli.create('pr').command('list', { run: () => ({}) })
|
|
136
|
+
cli.command(pr)
|
|
137
|
+
|
|
138
|
+
expect(Typegen.fromCli(cli)).toMatchInlineSnapshot(`
|
|
139
|
+
"declare module 'incur' {
|
|
140
|
+
interface Register {
|
|
141
|
+
commands: {
|
|
142
|
+
'ping': { args: {}; options: {} }
|
|
143
|
+
'pr list': { args: {}; options: {} }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
"
|
|
148
|
+
`)
|
|
149
|
+
})
|
|
150
|
+
})
|
package/src/Typegen.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import * as Cli from './Cli.js'
|
|
5
|
+
import { importCli } from './internal/utils.js'
|
|
6
|
+
|
|
7
|
+
/** Imports a CLI from `input` (must `export default` a `Cli`), generates the `.d.ts`, and writes it to `output`. */
|
|
8
|
+
export async function generate(input: string, output: string): Promise<void> {
|
|
9
|
+
const cli = await importCli(input)
|
|
10
|
+
await fs.writeFile(output, fromCli(cli))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Generates a `.d.ts` declaration string for the `incur` module augmentation. */
|
|
14
|
+
export function fromCli(cli: Cli.Cli): string {
|
|
15
|
+
const commands = Cli.toCommands.get(cli)
|
|
16
|
+
if (!commands) throw new Error('No commands registered on this CLI instance')
|
|
17
|
+
|
|
18
|
+
const entries = collectEntries(commands, [])
|
|
19
|
+
|
|
20
|
+
const lines: string[] = ["declare module 'incur' {", ' interface Register {', ' commands: {']
|
|
21
|
+
|
|
22
|
+
for (const { name, args, options } of entries)
|
|
23
|
+
lines.push(
|
|
24
|
+
` '${name}': { args: ${schemaToType(args)}; options: ${schemaToType(options)} }`,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
lines.push(' }', ' }', '}', '')
|
|
28
|
+
return lines.join('\n')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Recursively collects leaf commands with their full paths and schemas. */
|
|
32
|
+
function collectEntries(
|
|
33
|
+
commands: Map<string, any>,
|
|
34
|
+
prefix: string[],
|
|
35
|
+
): { name: string; args?: z.ZodObject<any>; options?: z.ZodObject<any> }[] {
|
|
36
|
+
const result: ReturnType<typeof collectEntries> = []
|
|
37
|
+
for (const [name, entry] of commands) {
|
|
38
|
+
const path = [...prefix, name]
|
|
39
|
+
if ('_group' in entry && entry._group) result.push(...collectEntries(entry.commands, path))
|
|
40
|
+
else result.push({ name: path.join(' '), args: entry.args, options: entry.options })
|
|
41
|
+
}
|
|
42
|
+
return result.sort((a, b) => a.name.localeCompare(b.name))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Converts a Zod object schema to a TypeScript type string. Returns `{}` for undefined schemas. */
|
|
46
|
+
function schemaToType(schema: z.ZodObject<any> | undefined): string {
|
|
47
|
+
if (!schema) return '{}'
|
|
48
|
+
const json = z.toJSONSchema(schema) as Record<string, unknown>
|
|
49
|
+
const defs = (json.$defs ?? {}) as Record<string, Record<string, unknown>>
|
|
50
|
+
const properties = json.properties as Record<string, Record<string, unknown>> | undefined
|
|
51
|
+
if (!properties || Object.keys(properties).length === 0) return '{}'
|
|
52
|
+
const entries = Object.entries(properties).map(
|
|
53
|
+
([key, value]) => `${key}: ${resolveType(value, defs)}`,
|
|
54
|
+
)
|
|
55
|
+
return `{ ${entries.join('; ')} }`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Recursively resolves a JSON Schema node to a TypeScript type string. */
|
|
59
|
+
function resolveType(
|
|
60
|
+
schema: Record<string, unknown>,
|
|
61
|
+
defs: Record<string, Record<string, unknown>>,
|
|
62
|
+
): string {
|
|
63
|
+
if (schema.$ref) {
|
|
64
|
+
const ref = (schema.$ref as string).replace('#/$defs/', '')
|
|
65
|
+
const resolved = defs[ref]
|
|
66
|
+
if (resolved) return resolveType(resolved, defs)
|
|
67
|
+
return 'unknown'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ('const' in schema) return JSON.stringify(schema.const)
|
|
71
|
+
if (schema.enum) return (schema.enum as unknown[]).map((v) => JSON.stringify(v)).join(' | ')
|
|
72
|
+
if (schema.anyOf)
|
|
73
|
+
return (schema.anyOf as Record<string, unknown>[]).map((s) => resolveType(s, defs)).join(' | ')
|
|
74
|
+
|
|
75
|
+
const type = schema.type as string | string[] | undefined
|
|
76
|
+
if (Array.isArray(type))
|
|
77
|
+
return type
|
|
78
|
+
.map((t) => (t === 'null' ? 'null' : resolveType({ ...schema, type: t }, defs)))
|
|
79
|
+
.join(' | ')
|
|
80
|
+
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'string':
|
|
83
|
+
return 'string'
|
|
84
|
+
case 'number':
|
|
85
|
+
case 'integer':
|
|
86
|
+
return 'number'
|
|
87
|
+
case 'boolean':
|
|
88
|
+
return 'boolean'
|
|
89
|
+
case 'null':
|
|
90
|
+
return 'null'
|
|
91
|
+
case 'array': {
|
|
92
|
+
const items = schema.items as Record<string, unknown> | undefined
|
|
93
|
+
const itemType = items ? resolveType(items, defs) : 'unknown'
|
|
94
|
+
return itemType.includes(' | ') ? `(${itemType})[]` : `${itemType}[]`
|
|
95
|
+
}
|
|
96
|
+
case 'object': {
|
|
97
|
+
const properties = schema.properties as Record<string, Record<string, unknown>> | undefined
|
|
98
|
+
if (!properties || Object.keys(properties).length === 0) return '{}'
|
|
99
|
+
const entries = Object.entries(properties).map(
|
|
100
|
+
([key, value]) => `${key}: ${resolveType(value, defs)}`,
|
|
101
|
+
)
|
|
102
|
+
return `{ ${entries.join('; ')} }`
|
|
103
|
+
}
|
|
104
|
+
default:
|
|
105
|
+
return 'unknown'
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
import * as Cli from './Cli.js'
|
|
6
|
+
import * as Typegen from './Typegen.js'
|
|
7
|
+
|
|
8
|
+
const cli = Cli.create('incur', {
|
|
9
|
+
description: 'CLI for incur',
|
|
10
|
+
sync: {
|
|
11
|
+
depth: 1,
|
|
12
|
+
include: ['_root'],
|
|
13
|
+
suggestions: ['build a cli with incur', 'generate incur types'],
|
|
14
|
+
},
|
|
15
|
+
}).command('gen', {
|
|
16
|
+
description: 'Generate type definitions for development.',
|
|
17
|
+
options: z.object({
|
|
18
|
+
dir: z.string().optional().describe('Project root directory'),
|
|
19
|
+
entry: z.string().optional().describe('Entrypoint path (absolute)'),
|
|
20
|
+
output: z.string().optional().describe('Output path (absolute)'),
|
|
21
|
+
}),
|
|
22
|
+
async run({ options }) {
|
|
23
|
+
const dir = options.dir ?? '.'
|
|
24
|
+
const entry = options.entry ?? dir
|
|
25
|
+
const output = options.output ?? path.join(dir, 'incur.generated.ts')
|
|
26
|
+
await Typegen.generate(entry, output)
|
|
27
|
+
return { dir, entry, output }
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
cli.serve()
|
|
32
|
+
|
|
33
|
+
export default cli
|