Cooklang API
Cook Editor exposes its Cooklang features to plugins as commands. Call them with vscode.commands.executeCommand(id, args). They run the same Rust code as the editor, so your plugin gets the same results as the built-in features.
Conventions
- Each command takes one JSON object and returns JSON.
- Paths are relative to the open recipe folder, e.g.
"Dinner/Carbonara.cook". Absolute paths (including Windows paths likeC:\Recipes\Bread.cook) andfile://URIs inside the folder also work, and relative paths may use\or/. - Arguments are checked strictly: paths must be non-empty strings, scales and multipliers positive numbers, and paths and ingredient names must not contain control characters such as newlines.
- Errors reject the promise with a readable message:
No workspace is open.,Recipe not found: …,Path is outside the workspace: …,Invalid arguments: …. - The commands don't appear in the command palette.
Version
| Command | Arguments | Returns |
|---|---|---|
cooklang.api.version | — | 1 |
Check it when your plugin activates. The same number is available to when clauses as the context key cooklang.apiVersion, e.g. "when": "cooklang.apiVersion == 1". Version 1 only grows: new commands and optional fields may appear, nothing is removed or renamed.
Shopping lists
| Command | Arguments | Returns |
|---|---|---|
cooklang.api.generateShoppingList |
{ recipes: [{ path, scale? }] } |
{ categories: [{ name, items: [{ name, quantities }] }], other: { name, items }, pantryItems: [name] }Categories follow config/aisle.conf; items in config/pantry.conf are moved to pantryItems. Recipes that can't be found are skipped. Pass every recipe you want counted — sub-recipes are not expanded here (use resolveRecipeReferences).
|
const list = await vscode.commands.executeCommand('cooklang.api.generateShoppingList', {
recipes: [{ path: 'Dinner/Carbonara.cook', scale: 2 }, { path: 'Bread.cook' }],
});
Recipe references
| Command | Arguments | Returns |
|---|---|---|
cooklang.api.resolveRecipeReferences |
{ path } — a .cook or .menu |
[{ path, scale, children? }] — the @recipe references, followed recursively. scale is a multiplier relative to the recipe holding the reference; {4%servings} and yield units are already converted. A reference cycle is skipped.
|
The shopping-list files
.shopping-list and .shopping-checked are the files CookCLI and the Shopping List plugin share. Use these commands to read and write them instead of parsing the text yourself.
| Command | Arguments | Returns |
|---|---|---|
cooklang.api.parseShoppingList | { text } | { items: [{ type: 'recipe', path, multiplier?, children }] } |
cooklang.api.writeShoppingList | { list } (that shape; children may be left out) | file text |
cooklang.api.parseShoppingChecked | { text } | [{ type: 'checked' | 'unchecked', name }] — later entries win |
cooklang.api.writeShoppingChecked | { entries } | file text, one line per entry (append it to the file) |
cooklang.api.compactShoppingChecked | { entries, ingredients: [name] } | the entries whose ingredient is still in ingredients |
The pantry file
config/pantry.conf is the pantry CookCLI, the Shopping List and the Pantry plugin share. These commands work on its text: read the file with vscode.workspace.fs, pass the text in, and write back what editPantry returns. Edits keep comments and formatting and touch only the item you name.
| Command | Arguments | Returns |
|---|---|---|
cooklang.api.parsePantry |
{ text } |
{ sections: [{ name, items: [{ name, quantity?, bought?, expire?, low?, isLow, isOutOfStock, expireDate?, boughtDate? }] }] }Attributes are the text as written ( "500%g") and left out when absent. expireDate/boughtDate are the dates as YYYY-MM-DD, left out when they can't be read. Items above the first [section] are in the section general.
|
cooklang.api.editPantry |
{ text, edit }, where edit is one of:{ op: 'add', section, name, quantity?, bought?, expire?, low? }{ op: 'update', section, name, fields: { quantity?, bought?, expire?, low? } }{ op: 'remove', section, name }
|
The new file text. On update, a field you leave out is unchanged and "" removes it. Adding creates the section if needed; removing a section's last item removes the section. Unknown keys are rejected. The promise rejects with a readable message for a missing section or item, a duplicate name (case-insensitive), or anything the editor can't change without rewriting parts of the file you didn't ask to touch.
|
const uri = vscode.Uri.joinPath(folder.uri, 'config/pantry.conf');
const text = new TextDecoder().decode(await vscode.workspace.fs.readFile(uri));
const updated = await vscode.commands.executeCommand('cooklang.api.editPantry', {
text,
edit: { op: 'update', section: 'fridge', name: 'milk', fields: { quantity: '2%L', expire: '' } },
});
await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(updated));
These commands were added to version 1 after it first shipped. Check that they exist with (await vscode.commands.getCommands(true)).includes('cooklang.api.editPantry') and ask users on an older Cook Editor to update.