incur 0.4.0 → 0.4.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/README.md +83 -22
- package/SKILL.md +6 -6
- package/dist/Cli.d.ts +46 -26
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +728 -441
- package/dist/Cli.js.map +1 -1
- package/dist/Completions.d.ts +4 -3
- package/dist/Completions.d.ts.map +1 -1
- package/dist/Completions.js +17 -10
- package/dist/Completions.js.map +1 -1
- package/dist/Fetch.d.ts.map +1 -1
- package/dist/Fetch.js +10 -9
- package/dist/Fetch.js.map +1 -1
- package/dist/Filter.js +0 -18
- package/dist/Filter.js.map +1 -1
- package/dist/Formatter.d.ts.map +1 -1
- package/dist/Formatter.js +6 -1
- package/dist/Formatter.js.map +1 -1
- package/dist/Help.d.ts +7 -1
- package/dist/Help.d.ts.map +1 -1
- package/dist/Help.js +44 -27
- package/dist/Help.js.map +1 -1
- package/dist/Mcp.d.ts +37 -5
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +71 -72
- package/dist/Mcp.js.map +1 -1
- package/dist/Openapi.d.ts.map +1 -1
- package/dist/Openapi.js +22 -14
- package/dist/Openapi.js.map +1 -1
- package/dist/Parser.d.ts +4 -0
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +70 -38
- package/dist/Parser.js.map +1 -1
- package/dist/Schema.d.ts +5 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +13 -2
- package/dist/Schema.js.map +1 -1
- package/dist/Skill.d.ts +2 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +33 -19
- 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 +48 -0
- package/dist/SyncSkills.d.ts.map +1 -1
- package/dist/SyncSkills.js +108 -10
- package/dist/SyncSkills.js.map +1 -1
- package/dist/Typegen.js +4 -2
- package/dist/Typegen.js.map +1 -1
- package/dist/bin.d.ts +2 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +17 -2
- package/dist/bin.js.map +1 -1
- package/dist/internal/command.d.ts +170 -0
- package/dist/internal/command.d.ts.map +1 -0
- package/dist/internal/command.js +292 -0
- package/dist/internal/command.js.map +1 -0
- package/dist/internal/configSchema.d.ts +8 -0
- package/dist/internal/configSchema.d.ts.map +1 -0
- package/dist/internal/configSchema.js +57 -0
- package/dist/internal/configSchema.js.map +1 -0
- package/dist/internal/dereference.d.ts +12 -0
- package/dist/internal/dereference.d.ts.map +1 -0
- package/dist/internal/dereference.js +71 -0
- package/dist/internal/dereference.js.map +1 -0
- package/dist/internal/helpers.d.ts +9 -0
- package/dist/internal/helpers.d.ts.map +1 -0
- package/dist/internal/helpers.js +54 -0
- package/dist/internal/helpers.js.map +1 -0
- package/dist/middleware.d.ts +6 -8
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.js.map +1 -1
- package/examples/npm/.npmrc.json +21 -0
- package/examples/npm/config.schema.json +134 -0
- package/package.json +6 -29
- package/src/Cli.test-d.ts +44 -33
- package/src/Cli.test.ts +1231 -101
- package/src/Cli.ts +877 -569
- package/src/Completions.test.ts +136 -12
- package/src/Completions.ts +18 -13
- package/src/Fetch.test.ts +21 -0
- package/src/Fetch.ts +8 -10
- package/src/Filter.ts +0 -17
- package/src/Formatter.test.ts +15 -2
- package/src/Formatter.ts +5 -1
- package/src/Help.test.ts +184 -20
- package/src/Help.ts +52 -28
- package/src/Mcp.test.ts +159 -0
- package/src/Mcp.ts +108 -86
- package/src/Openapi.test.ts +17 -5
- package/src/Openapi.ts +21 -15
- package/src/Parser.test-d.ts +22 -0
- package/src/Parser.test.ts +89 -0
- package/src/Parser.ts +87 -36
- package/src/Schema.test.ts +29 -0
- package/src/Schema.ts +12 -2
- package/src/Skill.test.ts +87 -6
- package/src/Skill.ts +38 -21
- package/src/Skillgen.ts +1 -1
- package/src/SyncMcp.test.ts +6 -8
- package/src/SyncSkills.test.ts +146 -3
- package/src/SyncSkills.ts +191 -10
- package/src/Typegen.test.ts +15 -0
- package/src/Typegen.ts +4 -2
- package/src/bin.ts +21 -2
- package/src/e2e.test.ts +188 -98
- package/src/internal/command.ts +449 -0
- package/src/internal/configSchema.test.ts +193 -0
- package/src/internal/configSchema.ts +66 -0
- package/src/internal/dereference.test.ts +695 -0
- package/src/internal/dereference.ts +75 -0
- package/src/internal/helpers.test.ts +75 -0
- package/src/internal/helpers.ts +59 -0
- package/src/middleware.ts +5 -12
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dereferences all local `$ref` pointers in a JSON object (e.g. `{"$ref": "#/components/schemas/User"}`),
|
|
3
|
+
* replacing them inline with the resolved values. Only handles local (`#/...`) references.
|
|
4
|
+
*
|
|
5
|
+
* Handles circular references by caching a mutable placeholder before recursing.
|
|
6
|
+
*
|
|
7
|
+
* Minimal reimplementation of the dereferencing behavior from `@apidevtools/json-schema-ref-parser`
|
|
8
|
+
* (https://github.com/APIDevTools/json-schema-ref-parser). Only supports in-memory, local-pointer
|
|
9
|
+
* resolution — no file/URL resolution, no `$id` scoping.
|
|
10
|
+
*/
|
|
11
|
+
export function dereference<value>(root: value): value {
|
|
12
|
+
const cache = new Map<string, unknown>()
|
|
13
|
+
return walk(root, root, cache) as value
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function walk(node: unknown, root: unknown, cache: Map<string, unknown>): unknown {
|
|
17
|
+
if (Array.isArray(node)) return node.map((item) => walk(item, root, cache))
|
|
18
|
+
|
|
19
|
+
if (typeof node !== 'object' || node === null) return node
|
|
20
|
+
|
|
21
|
+
const obj = node as Record<string, unknown>
|
|
22
|
+
|
|
23
|
+
// Resolve $ref pointer
|
|
24
|
+
if (typeof obj.$ref === 'string' && obj.$ref.startsWith('#')) {
|
|
25
|
+
const ref = obj.$ref
|
|
26
|
+
if (cache.has(ref)) return cache.get(ref)
|
|
27
|
+
|
|
28
|
+
const resolved = resolvePointer(root, ref)
|
|
29
|
+
|
|
30
|
+
// Non-object targets (primitives, arrays) can't be circular — resolve directly
|
|
31
|
+
if (typeof resolved !== 'object' || resolved === null || Array.isArray(resolved)) {
|
|
32
|
+
const dereferenced = walk(resolved, root, cache)
|
|
33
|
+
cache.set(ref, dereferenced)
|
|
34
|
+
return dereferenced
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Use a mutable placeholder so circular refs resolve to the same object.
|
|
38
|
+
// If the walked result is not a plain object (e.g. chained ref to primitive/array),
|
|
39
|
+
// skip the placeholder and cache directly.
|
|
40
|
+
const placeholder: Record<string, unknown> = {}
|
|
41
|
+
cache.set(ref, placeholder)
|
|
42
|
+
const dereferenced = walk(resolved, root, cache)
|
|
43
|
+
if (typeof dereferenced !== 'object' || dereferenced === null || Array.isArray(dereferenced)) {
|
|
44
|
+
cache.set(ref, dereferenced)
|
|
45
|
+
return dereferenced
|
|
46
|
+
}
|
|
47
|
+
Object.assign(placeholder, dereferenced)
|
|
48
|
+
return placeholder
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result: Record<string, unknown> = {}
|
|
52
|
+
for (const key of Object.keys(obj)) result[key] = walk(obj[key], root, cache)
|
|
53
|
+
return result
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Resolves a JSON Pointer (e.g. `#/components/schemas/User`) against a root object. */
|
|
57
|
+
function resolvePointer(root: unknown, pointer: string): unknown {
|
|
58
|
+
// "#" or "#/" → root
|
|
59
|
+
const fragment = pointer.slice(1)
|
|
60
|
+
if (fragment === '' || fragment === '/') return root
|
|
61
|
+
|
|
62
|
+
const parts = fragment
|
|
63
|
+
.slice(1)
|
|
64
|
+
.split('/')
|
|
65
|
+
.map((p) => p.replace(/~1/g, '/').replace(/~0/g, '~'))
|
|
66
|
+
|
|
67
|
+
let current: unknown = root
|
|
68
|
+
for (const part of parts) {
|
|
69
|
+
if (typeof current !== 'object' || current === null)
|
|
70
|
+
throw new Error(`Cannot resolve $ref "${pointer}": path segment "${part}" not found`)
|
|
71
|
+
current = (current as Record<string, unknown>)[part]
|
|
72
|
+
if (current === undefined) throw new Error(`Cannot resolve $ref "${pointer}": "${part}" not found`)
|
|
73
|
+
}
|
|
74
|
+
return current
|
|
75
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { levenshtein, suggest } from './helpers.js'
|
|
4
|
+
|
|
5
|
+
describe('levenshtein', () => {
|
|
6
|
+
test('identical strings', () => {
|
|
7
|
+
expect(levenshtein('abc', 'abc')).toBe(0)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('single insertion', () => {
|
|
11
|
+
expect(levenshtein('abc', 'abcd')).toBe(1)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('single deletion', () => {
|
|
15
|
+
expect(levenshtein('abcd', 'abc')).toBe(1)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('single substitution', () => {
|
|
19
|
+
expect(levenshtein('abc', 'axc')).toBe(1)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('transposition counts as 2', () => {
|
|
23
|
+
expect(levenshtein('mpc', 'mcp')).toBe(2)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('empty strings', () => {
|
|
27
|
+
expect(levenshtein('', '')).toBe(0)
|
|
28
|
+
expect(levenshtein('abc', '')).toBe(3)
|
|
29
|
+
expect(levenshtein('', 'abc')).toBe(3)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('suggest', () => {
|
|
34
|
+
const commands = ['deploy', 'status', 'list', 'create']
|
|
35
|
+
|
|
36
|
+
test('returns closest match within threshold', () => {
|
|
37
|
+
expect(suggest('deplyo', commands)).toBe('deploy')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('returns match for transposition', () => {
|
|
41
|
+
expect(suggest('mpc', ['mcp', 'skills', 'completions'])).toBe('mcp')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('returns undefined when no match is close enough', () => {
|
|
45
|
+
expect(suggest('xyz', commands)).toBeUndefined()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('returns undefined for empty candidates', () => {
|
|
49
|
+
expect(suggest('deploy', [])).toBeUndefined()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('picks the closest among multiple candidates', () => {
|
|
53
|
+
expect(suggest('craete', ['list', 'create', 'delete'])).toBe('create')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('matches unambiguous prefix', () => {
|
|
57
|
+
expect(suggest('ski', ['mcp', 'skills', 'completions'])).toBe('skills')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('prefers shorter prefix match among ambiguous prefixes', () => {
|
|
61
|
+
expect(suggest('ski', ['skills', 'skip'])).toBe('skip')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('prefix match beats fuzzy match', () => {
|
|
65
|
+
expect(suggest('iss', ['issue', 'is', 'miss'])).toBe('issue')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('contains match beats fuzzy match', () => {
|
|
69
|
+
expect(suggest('sue', ['issue', 'use', 'sum'])).toBe('issue')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('case-insensitive matching', () => {
|
|
73
|
+
expect(suggest('Deploy', ['deploy', 'list'])).toBe('deploy')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Checks whether a value is a plain object record. */
|
|
2
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Converts a camelCase string to kebab-case. */
|
|
7
|
+
export function toKebab(value: string): string {
|
|
8
|
+
return value.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Computes the Levenshtein edit distance between two strings. */
|
|
12
|
+
export function levenshtein(a: string, b: string): number {
|
|
13
|
+
const m = a.length
|
|
14
|
+
const n = b.length
|
|
15
|
+
const dp: number[] = Array.from({ length: n + 1 }, (_, i) => i)
|
|
16
|
+
for (let i = 1; i <= m; i++) {
|
|
17
|
+
let prev = dp[0]!
|
|
18
|
+
dp[0] = i
|
|
19
|
+
for (let j = 1; j <= n; j++) {
|
|
20
|
+
const tmp = dp[j]!
|
|
21
|
+
dp[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j]!, dp[j - 1]!)
|
|
22
|
+
prev = tmp
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return dp[n]!
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Suggests the closest command name from a set, returning it if within a reasonable edit distance. */
|
|
29
|
+
export function suggest(input: string, candidates: Iterable<string>): string | undefined {
|
|
30
|
+
const threshold = input.length <= 4 ? 2 : Math.floor(input.length / 2)
|
|
31
|
+
const lower = input.toLowerCase()
|
|
32
|
+
const all = Array.isArray(candidates) ? candidates : [...candidates]
|
|
33
|
+
|
|
34
|
+
let best: string | undefined
|
|
35
|
+
let bestScore = Infinity
|
|
36
|
+
|
|
37
|
+
for (const c of all) {
|
|
38
|
+
const lc = c.toLowerCase()
|
|
39
|
+
const dist = levenshtein(lower, lc)
|
|
40
|
+
|
|
41
|
+
let score: number
|
|
42
|
+
if (lc.startsWith(lower) && lc !== lower)
|
|
43
|
+
// prefix match — best tier
|
|
44
|
+
score = dist
|
|
45
|
+
else if (lc.includes(lower))
|
|
46
|
+
// contains match — middle tier
|
|
47
|
+
score = 100 + dist
|
|
48
|
+
else if (dist <= threshold)
|
|
49
|
+
// fuzzy match — last tier
|
|
50
|
+
score = 200 + dist
|
|
51
|
+
else continue
|
|
52
|
+
|
|
53
|
+
if (score < bestScore) {
|
|
54
|
+
bestScore = score
|
|
55
|
+
best = c
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return best
|
|
59
|
+
}
|
package/src/middleware.ts
CHANGED
|
@@ -10,16 +10,11 @@ type InferVars<vars extends z.ZodObject<any> | undefined> =
|
|
|
10
10
|
type InferEnv<env extends z.ZodObject<any> | undefined> =
|
|
11
11
|
env extends z.ZodObject<any> ? z.output<env> : {}
|
|
12
12
|
|
|
13
|
-
/** @internal Infers the output type of an options schema, or `{}` if undefined. */
|
|
14
|
-
type InferOptions<options extends z.ZodObject<any> | undefined> =
|
|
15
|
-
options extends z.ZodObject<any> ? z.output<options> : {}
|
|
16
|
-
|
|
17
13
|
/** Middleware handler that runs before/after command execution. */
|
|
18
14
|
export type Handler<
|
|
19
15
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
20
16
|
env extends z.ZodObject<any> | undefined = undefined,
|
|
21
|
-
|
|
22
|
-
> = (context: Context<vars, env, options>, next: () => Promise<void>) => Promise<void> | void
|
|
17
|
+
> = (context: Context<vars, env>, next: () => Promise<void>) => Promise<void> | void
|
|
23
18
|
|
|
24
19
|
/** CTA block for middleware error/ok responses. */
|
|
25
20
|
type CtaBlock = {
|
|
@@ -33,12 +28,13 @@ type CtaBlock = {
|
|
|
33
28
|
export type Context<
|
|
34
29
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
35
30
|
env extends z.ZodObject<any> | undefined = undefined,
|
|
36
|
-
options extends z.ZodObject<any> | undefined = undefined,
|
|
37
31
|
> = {
|
|
38
32
|
/** Whether the consumer is an agent (stdout is not a TTY). */
|
|
39
33
|
agent: boolean
|
|
40
34
|
/** The resolved command path. */
|
|
41
35
|
command: string
|
|
36
|
+
/** The binary name the user invoked (e.g. an alias). Falls back to `name` when not resolvable. */
|
|
37
|
+
displayName: string
|
|
42
38
|
/** Parsed environment variables from the CLI-level env schema. */
|
|
43
39
|
env: InferEnv<env>
|
|
44
40
|
/** Return an error result, short-circuiting the middleware chain. */
|
|
@@ -55,8 +51,6 @@ export type Context<
|
|
|
55
51
|
formatExplicit: boolean
|
|
56
52
|
/** The CLI name. */
|
|
57
53
|
name: string
|
|
58
|
-
/** Parsed options from the root CLI-level options schema. */
|
|
59
|
-
options: InferOptions<options>
|
|
60
54
|
/** Set a typed variable for downstream middleware and handlers. */
|
|
61
55
|
set<key extends string & keyof InferVars<vars>>(key: key, value: InferVars<vars>[key]): void
|
|
62
56
|
/** Variables set by upstream middleware. */
|
|
@@ -65,11 +59,10 @@ export type Context<
|
|
|
65
59
|
version: string | undefined
|
|
66
60
|
}
|
|
67
61
|
|
|
68
|
-
/** Creates a strictly typed middleware handler. Pass the vars schema as a generic for typed `c.set()` and `c.var`, the env schema for typed `c.env
|
|
62
|
+
/** Creates a strictly typed middleware handler. Pass the vars schema as a generic for typed `c.set()` and `c.var`, and the env schema for typed `c.env`. */
|
|
69
63
|
export default function middleware<
|
|
70
64
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
71
65
|
const env extends z.ZodObject<any> | undefined = undefined,
|
|
72
|
-
|
|
73
|
-
>(handler: Handler<vars, env, options>): Handler<vars, env, options> {
|
|
66
|
+
>(handler: Handler<vars, env>): Handler<vars, env> {
|
|
74
67
|
return handler
|
|
75
68
|
}
|