Skip to content

Commit 05b3b1e

Browse files
Aymericrclaude
andcommitted
docs(skills): add lingo agent skill (skills.sh)
Installable via `npx skills add pascalorg/lingo`. A when-to-use on-ramp with 0.2.0-verified examples (parse/convert/range, NL form field, safe LLM tool / MCP boundary) that points at llms.txt for the exhaustive reference. All API names, options, issue codes, and numeric outputs checked against the shipped library. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 84c359c commit 05b3b1e

2 files changed

Lines changed: 184 additions & 0 deletions

File tree

skills/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# lingo skills
2+
3+
Agent [skills](https://skills.sh) shipped with [`@pascal-app/lingo`](https://github.com/pascalorg/lingo).
4+
5+
## Install
6+
7+
```bash
8+
npx skills add pascalorg/lingo
9+
```
10+
11+
## Available skills
12+
13+
| Skill | Description |
14+
|-------|-------------|
15+
| [lingo](./lingo) | Parse natural-language quantities, units, dates & ranges into canonical, validated values — and humanize them back. For form inputs and safe LLM tool/MCP boundaries. |
16+
17+
## License
18+
19+
MIT

skills/lingo/SKILL.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
name: lingo
3+
description: Parse natural-language quantities, units, dates, and ranges ("5'11\"", "1.5 cups", "72 in to cm", "three days ago", "between 5 and 10 kg", "it's hot") into canonical, validated values — and humanize them back. Use when building form inputs that accept measurements/units/dates, making LLM tool calls safe at the boundary (AI SDK / MCP), or converting and validating any human- or model-entered measurement string. Zero-dependency TypeScript, two-way (round-trips), deterministic.
4+
metadata:
5+
author: pascalorg
6+
version: "1.0.0"
7+
---
8+
9+
# lingo — natural-language quantities, units, dates & ranges
10+
11+
[`@pascal-app/lingo`](https://github.com/pascalorg/lingo) is a zero-dependency
12+
TypeScript library that turns the strings people type and models emit —
13+
`180cm`, `5ft 11`, `1.5 cups`, `90 min`, `next friday`, `1,5 kg`, `"5'11\""`,
14+
`"twenty-five kg"`, `"3pm EST"` — into canonical, validated values (one number
15+
in one unit, one ISO date), converts and range-checks them, then humanizes the
16+
value back. Every result carries a stable issue code and a `[start, end)` span
17+
into the original input. It's **two-way**: whatever `format()`/`humanize*()`
18+
emits re-parses to the same value.
19+
20+
Tagline: **Make forms easier, LLM tools safer.**
21+
22+
## When to use this skill
23+
24+
Reach for lingo whenever a value arrives as free text and needs to become a
25+
trustworthy stored value:
26+
27+
- **Web forms** — you're about to build a number box + unit dropdown. Use one
28+
text field instead: `180cm`, `2 lb 3 oz`, `an hour and a half`, typos with
29+
did-you-mean, fuzzy words (`it's hot`).
30+
- **LLM tool / MCP boundaries** — a model returns `"5'11\""` or `"1,234 kg"` and
31+
your handler needs a safe number. Models emit strings more reliably than
32+
floats; lingo makes the string the safe path and rejects risky readings.
33+
- **Data pipelines / imports** — normalizing messy measurement or date columns
34+
into canonical units without `Number()`/`new Date()` silently guessing wrong.
35+
- **Anything that converts** (`72 in to cm`), **ranges** (`between 5 and 10 kg`,
36+
`under 10 minutes`, `10 ± 0.5 mm`), or **relative dates** (`three days ago`).
37+
38+
Do **not** reach for it for plain already-canonical numbers, or for currency
39+
conversion needing live FX (lingo returns `RATE_REQUIRED` — you inject rates).
40+
41+
## Install & entry points
42+
43+
```sh
44+
npm i @pascal-app/lingo # or bun / pnpm / yarn — zero runtime deps
45+
```
46+
47+
Import only what you need; each subpath is tree-shakeable.
48+
49+
| Entry | Use for |
50+
|-------|---------|
51+
| `@pascal-app/lingo` | core parse/convert: `lingo`, `parseQuantity`, `parseRange`, `convert` |
52+
| `@pascal-app/lingo/date` | dates & durations: `parseDate`, `parseDuration`, `humanizeDate` |
53+
| `@pascal-app/lingo/ai` | Standard Schema fields for LLM tools: `quantityField`, `dateField`, `lingoObject` |
54+
| `@pascal-app/lingo/mcp` | `lingoTool()` — MCP tool with validation before the handler |
55+
| `@pascal-app/lingo/dom` | headless `lingoInput()` controller for any `<input>` |
56+
| `@pascal-app/lingo/react` | `useLingoInput()` hook |
57+
| `@pascal-app/lingo/element` | `<lingo-input>` form-associated custom element |
58+
| `@pascal-app/lingo/complete` | ranked autocomplete `completions()` |
59+
| `@pascal-app/lingo/locales/{en,en-gb,es,fr,pt,zh,ja}` | opt-in parsing language packs |
60+
| `@pascal-app/lingo/catalog` | query units/kinds/currencies |
61+
62+
## Pattern 1 — parse, validate, convert (core)
63+
64+
```ts
65+
import { lingo, parseQuantity, parseRange } from "@pascal-app/lingo"
66+
67+
const height = parseQuantity("5'11\"", { kind: "length" })
68+
if (height.ok) height.quantity.to("m").value // 1.8034
69+
70+
lingo("72 in to cm") // { type: "conversion", converted: 182.88 cm }
71+
parseRange("between 5 and 10 kg", { kind: "mass" }) // range 5..10 kg
72+
```
73+
74+
`lingo(text, opts?)` returns a versioned union on `.type`
75+
(`quantity | range | conversion | number | failure`), serialized as flat v3 JSON
76+
(`{ schemaVersion: 3, ok, type, ...value, text, span, issues, confidence }`).
77+
Always branch on `.ok` / `.type` before reading the value, and surface
78+
`.issues[]` (each has `code`, `severity`, `message`, `span`) to the user.
79+
80+
Pass context to disambiguate: `{ kind, unit, currency, locale, system, strictness }`.
81+
`strictness: "confirm"` turns each assumption (typo fix, ambiguous number) into a
82+
failure carrying a `candidate`, so you can render a one-click confirmation.
83+
84+
## Pattern 2 — a natural-language form field
85+
86+
Headless controller — no styles shipped, canonicalizes on blur/Enter/submit,
87+
never rewrites while the user is mid-type (`2 f` is *incomplete*, not *invalid*):
88+
89+
```ts
90+
import { lingoInput } from "@pascal-app/lingo/dom"
91+
92+
const field = lingoInput(document.querySelector("#height"), {
93+
kind: "length", unit: "m", name: "height_m",
94+
})
95+
field.set("6ft")
96+
field.commit() // hidden <input name="height_m"> submits the canonical value
97+
// field.state → 'idle' | 'incomplete' | 'valid' | 'invalid'
98+
```
99+
100+
React: `useLingoInput(opts)` from `@pascal-app/lingo/react`. Framework-agnostic
101+
element: `defineLingoInput()` then `<lingo-input kind="length" unit="m" name="height_m">`.
102+
103+
## Pattern 3 — safe LLM tool / MCP boundary
104+
105+
The fields expose a **`string`** input JSON Schema (models are better at emitting
106+
`"5'11\""` than `1.8034`) and hand your handler the **canonical** value. Risky
107+
readings fail loudly with `[CODE] … Did you mean …?` messages a model can
108+
self-correct from in one round trip.
109+
110+
```ts
111+
import { tool, generateText } from "ai"
112+
import { lingoObject, quantityField, dateField } from "@pascal-app/lingo/ai"
113+
114+
const shipment = lingoObject({
115+
weight: quantityField({ kind: "mass", unit: "kg", min: 0, max: 500 }),
116+
deliverBy: dateField({ now: new Date() }), // relative dates REQUIRE now
117+
})
118+
119+
await generateText({
120+
model: "claude-opus-4-8",
121+
tools: { create_shipment: tool({ inputSchema: shipment, execute: run }) },
122+
})
123+
```
124+
125+
MCP: wrap the same fields with `lingoTool({ name, description, input, handler })`
126+
from `@pascal-app/lingo/mcp`; validation runs before `handler`, and failures
127+
return `{ isError: true }` with `[CODE]`-prefixed text.
128+
129+
## Rules that keep you out of trouble
130+
131+
1. **Keep measurements as strings in tool/form schemas.** Let lingo convert,
132+
validate, surface spans, and handle ambiguity — don't ask the model or user
133+
for a float.
134+
2. **Store the canonical value, display the humanized one.** `format()` /
135+
`humanizeDate()` round-trip back to the same value (`1.9999 m``6′7″`,
136+
never `5′12″`).
137+
3. **Always pass an explicit `now`** to `parseDate`/`dateField` for relative
138+
dates — never rely on `Date.now()`, so a queued or retried call can't drift
139+
across midnight. Reference-dependent input without `now``NOW_REQUIRED`.
140+
4. **No silent guesses.** Ambiguous input returns a deterministic best reading
141+
plus ranked `alternatives`/`candidate` and a warning code — show it, or use
142+
`strictness: "confirm"` / `"strict"` to force confirmation.
143+
5. **Currency conversion needs injected rates**`5 EUR to USD` returns
144+
`RATE_REQUIRED`; call `convertCurrency` with your own rates.
145+
6. **Locale packs are opt-in.** English is built in; for other languages
146+
`createLingo({ locales: [es, fr, …] })` or you'll get `LOCALE_NOT_LOADED`.
147+
148+
Common issue codes to handle: `UNKNOWN_UNIT`, `KIND_MISMATCH`, `UNIT_REQUIRED`,
149+
`AMBIGUOUS_NUMBER`, `AMBIGUOUS_UNIT`, `TYPO_CORRECTED`, `RANGE_MIN`/`RANGE_MAX`,
150+
`NOW_REQUIRED`, `TZ_IGNORED`, `RATE_REQUIRED`. Override any message via the
151+
`messages` option.
152+
153+
## Full reference
154+
155+
This skill is the on-ramp; the exhaustive API, the complete kind/unit list, all
156+
issue codes, and per-input examples live in the agent docs:
157+
158+
- **Offline** (after install): `node_modules/@pascal-app/lingo/llms.txt` — a
159+
compressed, self-contained reference.
160+
- **Online:** [`lingo.pascal.app/llms.txt`](https://lingo.pascal.app/llms.txt)
161+
(index) → [`/docs/<section>.md`](https://lingo.pascal.app/docs/parse.md)
162+
per-topic, or [`/llms-full.txt`](https://lingo.pascal.app/llms-full.txt) for
163+
the complete narrative. Human docs with live demos:
164+
[`lingo.pascal.app/docs`](https://lingo.pascal.app/docs).
165+
- **Repo & README:** [github.com/pascalorg/lingo](https://github.com/pascalorg/lingo).

0 commit comments

Comments
 (0)