{"id":48915397,"url":"https://github.com/hakimjonas/lambe","last_synced_at":"2026-04-25T23:11:03.722Z","repository":{"id":351099537,"uuid":"1206412085","full_name":"hakimjonas/lambe","owner":"hakimjonas","description":"Lambë a universal query language for structured data","archived":false,"fork":false,"pushed_at":"2026-04-17T00:39:02.000Z","size":109,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-17T02:40:52.462Z","etag":null,"topics":["data-transformation","hcl2","json","mcp","pipeline","query","querydsl","repl","xml","yaml"],"latest_commit_sha":null,"homepage":"https://ardaproject.org/valar","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hakimjonas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-09T22:21:46.000Z","updated_at":"2026-04-17T00:39:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hakimjonas/lambe","commit_stats":null,"previous_names":["hakimjonas/lambe"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/hakimjonas/lambe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hakimjonas%2Flambe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hakimjonas%2Flambe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hakimjonas%2Flambe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hakimjonas%2Flambe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hakimjonas","download_url":"https://codeload.github.com/hakimjonas/lambe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hakimjonas%2Flambe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32185385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T11:42:27.955Z","status":"ssl_error","status_checked_at":"2026-04-23T11:42:18.877Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-transformation","hcl2","json","mcp","pipeline","query","querydsl","repl","xml","yaml"],"created_at":"2026-04-17T02:04:21.077Z","updated_at":"2026-04-25T23:11:03.712Z","avatar_url":"https://github.com/hakimjonas.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lambë\n\n*Query structured data, get errors with suggested fixes, and reshape results to the format you need.*\n\nLambë is a query language for JSON, YAML, TOML, HCL, CSV, TSV, and Markdown. Queries compose through a pipe operator, the same way a shell pipeline does. What's different: when a query produces a result your target format cannot serialize, Lambë infers the shape, explains the mismatch, and lists the curated query fragments that bridge it. The `as(fmt)` operator lets you ask for the bridge directly in the query language; `--explain` shows the shape at every pipe stage without running anything.\n\n```\n$ lam --to toml '.dependencies | keys' pubspec.yaml\nError: TOML output requires a map at the root, got list\u003cstring\u003e.\nTry appending one of:\n  | {items: .}    # Produces a map with one entry named \"items\".\n\n$ lam --to toml '.dependencies | keys | as(toml)' pubspec.yaml\nitems = [\"rumil\", \"rumil_parsers\", \"rumil_expressions\"]\n```\n\n*Lambë (pronounced \"lam-beh\") means \"language\" in Quenya (Tolkien's elvish). The package name is `lambe` for ASCII compatibility.*\n\n## Installation\n\n```bash\n# Pre-built binary (no Dart required)\ncurl -L https://github.com/hakimjonas/lambe/releases/latest/download/lam-linux-x64 -o lam\nchmod +x lam \u0026\u0026 sudo mv lam /usr/local/bin/\n\n# From pub.dev (Dart users)\ndart pub global activate lambe\n\n# Dart library\ndart pub add lambe\n\n# Build from source\ngit clone https://github.com/hakimjonas/lambe.git \u0026\u0026 cd lambe\ndart compile exe bin/lam.dart -o lam\n```\n\nSee [Getting started](doc/getting-started.md) for all installation options.\n\n## Shape-aware output\n\nLambë checks the result of your query against the shape the target format can serialize. When they match, output is produced. When they don't, the error names the required shape and lists query fragments that would bridge it. In an interactive terminal, Lambë offers to apply the chosen fragment and retry in place.\n\n```\n$ lam --to toml '.name' pubspec.yaml\nTOML output requires a map at the root, got string.\nTry appending one of:\n  | {value: .}    # Produces a map with one entry named \"value\".\n\nApply a bridge?\n  [1] | {value: .}    # Produces a map with one entry named \"value\".\n  [q] cancel\n\u003e 1\nvalue = \"rumil\"\n```\n\nThe same flow applies to CSV and TSV (which require a list of records at the root) and HCL (which requires a map). Suggestions are curated query fragments parsed to AST at construction, so the text you see is the code that runs.\n\n### `as(fmt)` — bridging in the query language\n\nWhen the shape of the target format is known up front, `as(fmt)` performs the bridge inside the query. The combinator is a no-op when the input already satisfies the target, applies a single curated bridge when one exists, and lists the candidates when more than one could apply.\n\n```\n$ lam --to toml '.dependencies | as(toml)' pubspec.yaml\nrumil = \"^0.5.0\"\nrumil_parsers = \"^0.5.0\"\nrumil_expressions = \"^0.5.0\"\n\n$ lam --to csv '.dependencies | as(csv)' pubspec.yaml\nkey,value\nrumil,^0.5.0\nrumil_parsers,^0.5.0\nrumil_expressions,^0.5.0\n```\n\n`as` accepts `json`, `yaml`, `toml`, `csv`, `tsv`, and `hcl`.\n\n### `--explain` — see the shape at every pipe stage\n\n`--explain` walks the pipe backbone of a query and reports the shape at each stage, followed by the set of output formats the final shape can be serialized as. It performs static analysis only and does not evaluate the query; pass a data file to seed with real shape information, or omit it to trace against an unknown input.\n\n```\n$ lam --explain '.dependencies | keys' pubspec.yaml\n.dependencies  : map\u003crumil: string, rumil_parsers: string, rumil_expressions: string\u003e\n| keys         : list\u003cstring\u003e\n\nWritable as: json, yaml, csv, tsv\nNot writable as: toml, hcl\n```\n\n## Query Syntax\n\nQueries start with `.` (the current data) and chain operations with `|`:\n\n```\n.                              the whole document\n.name                          access a field\n.users[0]                      index into a list\n.users[0].address.city         chain access\n.users | filter(.age \u003e 30)     pipe into an operation\n.users | map(.name)            transform each element\n```\n\nPipelines read left to right. Each `|` passes its result to the next operation:\n\n```\n.users | filter(.active) | sort_by(.name) | map(.name)\n```\n\nThis takes `.users`, keeps active ones, sorts by name, and extracts names.\n\n### Expressions\n\n```\n.price * .qty                  arithmetic (+, -, *, /, %)\n.age \u003e 30                      comparison (\u003c, \u003e, \u003c=, \u003e=, ==, !=)\n.active \u0026\u0026 .verified           logic (\u0026\u0026, ||, !)\nif .age \u003e 65 then \"senior\" else \"active\"   conditional\n{name, total: .price * .qty}   construct a new object\n\"\\(.name) is \\(.age)\"          string interpolation\n.[1:3]                         slice a list or string\n```\n\n### Operations\n\nOperations follow `|` and transform the piped value:\n\n```\n. | filter(.age \u003e 30)          keep matching elements\n. | map(.name)                 transform each element\n. | sort_by(.age)              sort by a key\n. | group_by(.dept)            group into [{key, values}]\n. | length                     count elements\n. | first                      first element\n. | sum                        sum numbers\n. | keys                       map keys or list indices\n. | has(\"field\")               check if a field exists\n. | unique                     remove duplicates\n. | flatten                    flatten one level of nesting\n. | to_entries                 map to [{key, value}] pairs\n. | filter_values(. \u003e 5)       filter a map's values\n. | as(toml)                   bridge to an output format\n```\n\nSee the full list in [Pipeline Operations](#pipeline-operations) below.\n\n## CLI\n\n```bash\n# Extract values\nlam '.database.host' config.toml\nlam '.spec.containers[0].image' deployment.yaml\n\n# Filter and transform\nlam '.users | filter(.age \u003e 30) | map(.name)' data.json\n\n# Aggregate\nlam '.items | map(.price) | sum' data.json\n\n# Sort and pick\nlam '.items | sort_by(.price) | first' data.json\n\n# Object construction\nlam '.users | map({name, senior: .age \u003e 65})' data.json\n\n# String interpolation\nlam '.users | map(\"\\(.name) is \\(.age)\")' data.json\n\n# Shape trace\nlam --explain '.users | map(.name)' data.json\n\n# Schema inference\nlam --schema data.json\n\n# CI validation\nlam --assert '.version != \"0.0.0\"' package.json\nlam --assert '.replicas \u003e= 2' deployment.yaml\n\n# Format conversion\nlam --to yaml '.config' data.json\nlam --to csv '.users | map({name, age})' data.json\nlam --to toml '.config | as(toml)' data.json\n\n# Query any format (auto-detected from extension)\nlam '. | filter(.status != \"closed\")' issues.csv\nlam '.resource | map(._labels)' main.tf\nlam '.children | filter(.type == \"heading\") | map(.children[0].text)' README.md\n\n# Pipe from stdin\ncurl -s https://api.example.com/users | lam '.results | filter(.active)'\n```\n\n## Interactive REPL\n\n```bash\nlam -i data.json\n```\n\n```\nlambe v0.6.0 - type :help for commands, :q to quit\nData loaded: {3 fields, 42 users}\n\nlambe\u003e .users | filter(.age \u003e 30) | map(.name)\n[\"Bob\", \"Carol\"]\n\nlambe\u003e .users[0]\n{name: \"Alice\", age: 25, active: true}\n\nlambe\u003e :schema\n{users: [{name: \"string\", age: \"number\", active: \"boolean\"}]}\n\nlambe\u003e :to yaml\nOutput format: yaml\n```\n\nWhen a query produces a result the current output format cannot serialize, the REPL lists the available bridges inline; pressing the number of a suggestion applies it and prints the bridged output. Tab completion works on field names (`.us\u003cTAB\u003e`) and pipeline operations (`| fil\u003cTAB\u003e`). The REPL also supports syntax highlighting, persistent history (`~/.lambe_history`), Ctrl+R reverse search, and multi-line input with `\\` continuation.\n\n## Library\n\n```dart\nimport 'package:lambe/lambe.dart';\n\n// Query pre-parsed data\nfinal name = query('.users[0].name', data);\n\n// Query a JSON string\nfinal version = queryJson('.version', '{\"version\": \"1.0.0\"}');\n\n// Query any format\nfinal host = queryString('.database.host', tomlString, format: Format.toml);\n\n// Parse once, evaluate many times\nfinal ast = parseAst('.users | filter(.active) | map(.name)');\nfinal result1 = evaluateAst(ast, dataset1);\nfinal result2 = evaluateAst(ast, dataset2);\n\n// Format conversion\nfinal yaml = formatOutput(data, OutputFormat.yaml);\nfinal csv = formatOutput(users, OutputFormat.csv);\n\n// Schema inference\nfinal schema = inferSchema(data);\n```\n\n### Shape and bridging API\n\n```dart\n// Infer the structural shape of a value\nfinal shape = shapeOf(data);\n// e.g. SMap({'users': SList(SMap({'name': SString(), 'age': SNum()}))})\n\n// Check whether a value can be written in a given format\nfinal report = canWriteAs(result, OutputFormat.toml);\nswitch (report) {\n  case Writable():\n    stdout.writeln(formatOutput(result, OutputFormat.toml));\n  case NotWritable(:final suggestions):\n    for (final r in suggestions) {\n      print('${r.label}: | ${r.display} — ${r.explanation}');\n    }\n}\n\n// Compose a user query with a bridge fragment\nfinal bridges = synthesize(shape, OutputFormat.csv);\nif (bridges.isNotEmpty) {\n  final composed = applyBridge(userAst, bridges.first);\n  final bridged = evaluateAst(composed, data);\n}\n\n// Static shape trace\nfinal trace = explain(parseAst('.users | map(.name)'), shapeOf(data));\nfor (final stage in trace.stages) {\n  print('${stage.source}: ${renderShape(stage.shape)}');\n}\n```\n\n## Supported Formats\n\n| Format | Input | Output | Conformance |\n|--------|:-----:|:------:|-------------|\n| JSON | yes | yes | RFC 8259 (318/318) |\n| YAML | yes | yes | YAML 1.2.2 (333/333) |\n| TOML | yes | yes | TOML 1.1 (681/681) |\n| HCL/Terraform | yes | yes | HashiCorp spec (2760/2760) |\n| CSV | yes | yes | RFC 4180 + auto-dialect detection |\n| TSV | yes | yes | Tab-separated variant of CSV |\n| Markdown | yes | — | CommonMark 0.31.2 (652/652) |\n\nParsers from [rumil_parsers](https://pub.dev/packages/rumil_parsers), tested against official spec suites.\n\nMarkdown is input-only in this release. The Markdown AST is a presentation tree rather than a data structure, so there is no general-purpose mapping from arbitrary query results back to Markdown text. Projections of a Markdown document (lists of headings, counts, filtered sections) emit as JSON, YAML, CSV, or TSV through the usual `--to` flag.\n\n## Pipeline Operations\n\n| Operation | Example | Description |\n|-----------|---------|-------------|\n| `filter` | `.users \\| filter(.active)` | Keep elements matching predicate |\n| `map` | `.users \\| map(.name)` | Transform each element |\n| `sort` | `. \\| sort` | Sort naturally |\n| `sort_by` | `.users \\| sort_by(.age)` | Sort by key |\n| `group_by` | `.users \\| group_by(.dept)` | Group into `{key, values}` |\n| `unique` | `. \\| unique` | Remove duplicates |\n| `unique_by` | `.users \\| unique_by(.id)` | Remove duplicates by key |\n| `flatten` | `. \\| flatten` | Flatten one level |\n| `reverse` | `. \\| reverse` | Reverse order |\n| `keys` | `. \\| keys` | Map keys or list indices |\n| `values` | `. \\| values` | Map values |\n| `length` | `. \\| length` | Length of list, map, or string |\n| `first` | `. \\| first` | First element |\n| `last` | `. \\| last` | Last element |\n| `sum` | `. \\| sum` | Sum numbers |\n| `avg` | `. \\| avg` | Average |\n| `min` | `. \\| min` | Minimum |\n| `max` | `. \\| max` | Maximum |\n| `has` | `. \\| has(\"name\")` | Check field exists |\n| `to_entries` | `. \\| to_entries` | Map to `[{key, value}]` |\n| `from_entries` | `. \\| from_entries` | `[{key, value}]` to map |\n| `to_number` | `.price \\| to_number` | Parse a string as a number |\n| `type` | `. \\| type` | Runtime type as a string |\n| `filter_values` | `. \\| filter_values(. \u003e 5)` | Filter map values |\n| `map_values` | `. \\| map_values(. * 2)` | Transform map values |\n| `filter_keys` | `. \\| filter_keys(. != \"secret\")` | Filter map keys |\n| `as` | `. \\| as(toml)` | Bridge to an output format's shape |\n\n## AI Integration\n\nLambë includes an MCP server for use with AI coding assistants.\n\n### MCP Server\n\nInstall, then add `.mcp.json` to your project:\n\n```json\n{\n  \"mcpServers\": {\n    \"lambe\": {\n      \"command\": \"lam-mcp\",\n      \"args\": []\n    }\n  }\n}\n```\n\nThis gives AI assistants three tools: `lambe_query` (extract/filter/transform), `lambe_schema` (structure inspection), `lambe_assert` (validation). When `lambe_query` encounters a shape mismatch with the requested output format, the error response includes a structured `suggestions` array: each entry carries a `template_text`, an `apply_as` (the complete query formed by appending the template to the original expression), and a one-line `explanation`. Agents can call the tool again with an `apply_as` verbatim.\n\n### For AI Coding Agents\n\nAdd [AGENTS.md](AGENTS.md) and `.mcp.json` to your project root. AI assistants that open the project will discover and use Lambë for data queries.\n\n### In CI\n\n```yaml\n# Validate config in GitHub Actions\n- run: |\n    dart pub global activate lambe\n    lam --assert '.version != \"0.0.0\"' pubspec.yaml\n    lam --assert '.jobs | keys | length \u003e 0' .github/workflows/ci.yml\n```\n\n## Test Matchers\n\nThe [lambe_test](lambe_test/) package provides test matchers for Dart:\n\n```dart\nimport 'package:lambe_test/lambe_test.dart';\n\nexpect(response, lamWhere('.errors | length == 0'));\nexpect(config, lamEquals('.database.port', 5432));\nexpect(data, lamMatches('.name', startsWith('A')));\nexpect(data, lamHas('.users[0].address.city'));\n```\n\n## Documentation\n\n- [Getting started](doc/getting-started.md) - install and first queries\n- [Syntax reference](doc/syntax.md) - the full query language\n- [REPL guide](doc/repl.md) - interactive mode, commands, keyboard shortcuts\n- [Recipes](doc/recipes.md) - real-world patterns for Kubernetes, Terraform, CI, CSV\n- [Man page](doc/lam.1.md) - Unix man page (`man -l doc/lam.1`)\n\n## Design\n\nSee [DESIGN.md](DESIGN.md) for architecture and design decisions.\n\n## Part of the Arda Ecosystem\n\nBuilt on [Rumil](https://pub.dev/packages/rumil) parser combinators with left-recursive grammar support.\n\n- [Rumil](https://pub.dev/packages/rumil) - parser combinators with left recursion\n- [Rumil Parsers](https://pub.dev/packages/rumil_parsers) - format parsers for JSON, YAML, TOML, XML, CSV, HCL, Proto3, Markdown\n- [Rumil Expressions](https://pub.dev/packages/rumil_expressions) - shared evaluation helpers\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhakimjonas%2Flambe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhakimjonas%2Flambe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhakimjonas%2Flambe/lists"}