{"id":51265979,"url":"https://github.com/devagrawal09/harlan","last_synced_at":"2026-06-29T15:02:03.215Z","repository":{"id":357947390,"uuid":"1239229958","full_name":"devagrawal09/harlan","owner":"devagrawal09","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-15T00:38:42.000Z","size":2788,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-15T01:32:54.809Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devagrawal09.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-05-14T22:23:28.000Z","updated_at":"2026-05-15T00:38:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/devagrawal09/harlan","commit_stats":null,"previous_names":["devagrawal09/harlan"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/devagrawal09/harlan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devagrawal09%2Fharlan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devagrawal09%2Fharlan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devagrawal09%2Fharlan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devagrawal09%2Fharlan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devagrawal09","download_url":"https://codeload.github.com/devagrawal09/harlan/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devagrawal09%2Fharlan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34931592,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-06-29T15:01:59.937Z","updated_at":"2026-06-29T15:02:03.205Z","avatar_url":"https://github.com/devagrawal09.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Harlan\n\nHarlan is a small TypeScript CLI for running an agent that can respond to tasks by writing and executing Harlan code.\n\nHarlan is also the custom language behind that tool: a small, immutable, ML-flavored workflow language for composing tool calls. The first MVP is focused on local filesystem, shell, and text-processing workflows that an agent can write, run, and reuse.\n\n## Requirements\n\n- Node.js 23.6 or newer\n- An API key for the model provider you use\n\n## Setup\n\nInstall dependencies:\n\n```sh\nnpm install\n```\n\nCreate a local environment file:\n\n```sh\ncp .env.example .env\n```\n\nThen fill in the required API key. The default model uses OpenRouter, so `OPENROUTER_API_KEY` is required unless you change `HARLAN_MODEL` to another provider.\n\n## Usage\n\nRun Harlan with a task:\n\n```sh\nnpm start -- \"write a short greeting\"\n```\n\nOr pipe a task through standard input:\n\n```sh\necho \"write a short greeting\" | npm start\n```\n\nChoose a model explicitly:\n\n```sh\nnpm start -- --model openrouter/google/gemini-2.0-flash-lite-001 \"write a short greeting\"\n```\n\n## Harlan Language\n\nHarlan programs are expression-oriented. A program can define immutable bindings with `let`, define functions with `fn`, call tools, and return the value of the final expression.\n\nModules are loaded with the built-in `import` function:\n\n```harlan\nlet fs = import(\"fs\")\nlet text = import(\"text\")\n\nfs.read(\"README.md\")\n  |\u003e text.lines()\n  |\u003e text.take(3)\n```\n\nWorkspace-local user libraries live under `harlan/` and are imported with dot specifiers:\n\n```harlan\nimport(\"mymodule.hello\")\n\nmymodule.hello.greet(\"Ada\")\n```\n\n`import(\"mymodule.hello\")` maps to `harlan/mymodule/hello.harlan` under the runtime working directory. User library imports are side-effecting: they create or extend the persistent namespace tree, so they do not need to be assigned to a `let` binding.\n\nThe pipeline operator passes the value on the left as the first argument to the function call on the right:\n\n```harlan\n\"a\\nb\\nc\"\n  |\u003e text.lines()\n  |\u003e text.take(2)\n```\n\nBindings are immutable:\n\n```harlan\nlet path = \"README.md\"\nlet body = fs.read(path)\n```\n\nFunctions can have simple type annotations:\n\n```harlan\nfn first_lines(path: String, count: Number) -\u003e List[String] =\n  fs.read(path)\n    |\u003e text.lines()\n    |\u003e text.take(count)\n\nfirst_lines(\"README.md\", 5)\n```\n\nRecords and lists are supported:\n\n```harlan\nlet task = {\n  path: \"README.md\",\n  count: 5\n}\n\nlet files = [\"README.md\", \"package.json\"]\n\ntask.path\n```\n\n## Script Logic\n\nUse `if` expressions to branch on tool results:\n\n```harlan\nlet fs = import(\"fs\")\n\nif fs.exists(\"README.md\") then\n  fs.read(\"README.md\")\nelse\n  \"README.md is missing\"\n```\n\nComparisons and boolean operators work with explicit booleans:\n\n```harlan\nlet fs = import(\"fs\")\nlet info = fs.info(\"README.md\")\n\ninfo.kind == \"file\" and info.size \u003e 0\n```\n\nDestructuring binds structured records and lists returned by helpers:\n\n```harlan\nlet fs = import(\"fs\")\nlet format = import(\"format\")\n\nlet { matches, truncated } = fs.search(\"src\", \"runHarlan\")\n\nif truncated then\n  \"too many results\"\nelse\n  format.table(matches)\n```\n\nSyntax summary:\n\n- `if condition then a else b`\n- `==`, `!=`, `\u003c`, `\u003c=`, `\u003e`, `\u003e=`\n- `and`, `or`, `not`\n- `null`\n- `let { field } = record`\n- `let [first] = list`\n\n## Built-in Modules\n\n`fs` provides local filesystem access inside the runtime working directory:\n\n```harlan\nlet fs = import(\"fs\")\n\nfs.cwd()\nfs.read(\"README.md\")\nfs.list(\".\")\nfs.exists(\"README.md\")\nfs.glob(\"src/**/*.ts\")\nfs.search(\"src\", \"execute_harlan\")\nfs.info(\"README.md\")\n```\n\n`text` provides small text/list helpers:\n\n```harlan\nlet text = import(\"text\")\n\ntext.lines(\"a\\nb\")\ntext.join([\"a\", \"b\"], \",\")\ntext.take([\"a\", \"b\", \"c\"], 2)\ntext.contains(\"abc\", \"b\")\ntext.trim(\"  abc  \")\ntext.lower(\"ABC\")\ntext.includes([\"a\", \"b\"], \"b\")\n```\n\n`format` turns Harlan values into readable strings:\n\n```harlan\nlet format = import(\"format\")\n\nformat.json({ path: \"README.md\", count: 3 })\nformat.lines([\"README.md\", \"package.json\"])\nformat.table([{ path: \"src/cli.ts\", line: 74 }])\n```\n\n`shell` runs local shell commands when shell execution is enabled by the host:\n\n```harlan\nlet shell = import(\"shell\")\n\nshell.run(\"printf hello\")\n```\n\n| Module   | Functions                                                        |\n| -------- | ---------------------------------------------------------------- |\n| `fs`     | `cwd`, `read`, `list`, `exists`, `glob`, `search`, `info`        |\n| `text`   | `lines`, `join`, `take`, `contains`, `trim`, `lower`, `includes` |\n| `format` | `json`, `lines`, `table`                                         |\n| `shell`  | `run`                                                            |\n\n## User Libraries\n\nA user library file may contain private top-level `let` bindings, imports, top-level `fn` declarations, and an optional final expression. Only top-level functions are exported.\n\n```harlan\n// harlan/mymodule/hello.harlan\nlet text = import(\"text\")\n\nfn greet(name: String) -\u003e String =\n  text.trim(name)\n```\n\nImport it once in a session:\n\n```harlan\nimport(\"mymodule.hello\")\n```\n\nThen call exported functions through the namespace:\n\n```harlan\nmymodule.hello.greet(\"  Ada  \")\n```\n\nImport results render signatures, not implementation bodies:\n\n```text\nImported mymodule.hello:\n- greet(name: String) -\u003e String\n```\n\nFunction values also render without bodies:\n\n```text\n\u003cfunction mymodule.hello.greet(name: String) -\u003e String\u003e\n```\n\nUse `revealImpl` only when the function body is needed:\n\n```harlan\nrevealImpl(mymodule.hello.greet)\n```\n\n`revealImpl` accepts a user-library function value and reveals the source for that function declaration in the tool result, similar to import disclosures. It returns `null`, so it cannot be used to capture source as a string. It does not reveal private top-level `let` bindings or unrelated functions, rejects stdlib or normal session-defined functions, and is intended for agent code or `harlan/init.harlan`, not user libraries.\n\nSlash paths are not valid user-library imports. Use `import(\"mymodule.hello\")`, not `import(\"mymodule/hello\")`.\n\n## Session Init\n\nPersistent agent sessions run `harlan/init.harlan` once before the first session Harlan execution, if the file exists. It is useful for preloading library namespaces:\n\n```harlan\nimport(\"mymodule.hello\")\nimport(\"repo.search\")\n```\n\nSuccessful init state is saved into the session snapshot before the requested code runs. If init fails, Harlan returns a warning with the init error and continues running the requested code with the pre-init snapshot.\n\n## Agent Workflow Examples\n\nPrefer Harlan's built-in inspection helpers before reaching for shell commands. Use `fs.glob` for file discovery, `fs.search` for code search, and `format.table` for compact structured results. Use `shell.run` only when the built-in modules are not enough.\n\nSearch code and return a Markdown table:\n\n```harlan\nlet fs = import(\"fs\")\nlet format = import(\"format\")\n\nfs.search(\"src\", \"execute_harlan\").matches\n  |\u003e format.table()\n```\n\nList source files:\n\n```harlan\nlet fs = import(\"fs\")\nlet format = import(\"format\")\n\nfs.glob(\"src/**/*.ts\")\n  |\u003e format.lines()\n```\n\nSummarize the beginning of the README:\n\n```harlan\nlet fs = import(\"fs\")\nlet text = import(\"text\")\n\nfs.read(\"README.md\")\n  |\u003e text.lines()\n  |\u003e text.take(10)\n```\n\nExample scripts live in `examples/`:\n\n- `examples/readme-summary.harlan`\n- `examples/search-code.harlan`\n- `examples/list-source.harlan`\n\n## Runtime API\n\nThe language can be used directly from TypeScript:\n\n```ts\nimport { renderHarlanResult, runHarlan } from \"./src/harlan/index.ts\";\n\nconst result = await runHarlan(\n  `\n    let fs = import(\"fs\")\n    let text = import(\"text\")\n\n    fs.read(\"README.md\")\n      |\u003e text.lines()\n      |\u003e text.take(3)\n  `,\n  { cwd: process.cwd(), allowShell: true },\n);\n\nconsole.log(renderHarlanResult(result));\n```\n\n## Development\n\n```sh\nnpm run typecheck\nnpm run lint\nnpm test\nnpm run format:check\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevagrawal09%2Fharlan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevagrawal09%2Fharlan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevagrawal09%2Fharlan/lists"}