{"_id":"@aeye/ginny","name":"@aeye/ginny","dist-tags":{"latest":"0.3.9"},"versions":{"0.3.9":{"name":"@aeye/ginny","version":"0.3.9","description":"Ginny — CLI that turns natural-language requests into executable gin programs","type":"module","main":"dist/index.js","types":"dist/index.d.ts","bin":{"ginny":"dist/index.js"},"scripts":{"build":"npm run clean && node esbuild.config.cjs","dev":"tsx src/index.ts","start":"tsx --conditions=source src/index.ts","clean":"rimraf dist tsconfig.tsbuildinfo","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","postinstall":"echo '\nGinny installed! Run \"ginny\" in any project directory to start.\nOn first run, a config.json template will be created — populate it with your provider API keys.\n'"},"keywords":["cli","ai","gin","agent","code-generation"],"author":{"name":"ClickerMonkey"},"license":"GPL-3.0","repository":{"type":"git","url":"git+https://github.com/ClickerMonkey/aeye.git","directory":"packages/ginny"},"dependencies":{"@tavily/core":"^0.5.13","mammoth":"^1.8.0","node-html-markdown":"^1.3.0","pdf-parse":"^1.1.1","xlsx":"^0.18.5"},"optionalDependencies":{"puppeteer":"^24.0.0"},"devDependencies":{"@aeye/ai":"0.3.9","@aeye/aws":"0.3.9","@aeye/core":"0.3.9","@aeye/gin":"0.3.9","@aeye/models":"0.3.9","@aeye/openai":"0.3.9","@aeye/openrouter":"0.3.9","@types/node":"^24.9.1","@types/pdf-parse":"^1.1.0","esbuild":"^0.26.0","rimraf":"^6.1.2","tsx":"^4.20.6","typescript":"^5.9.3","zod":"^4.1.12"},"publishConfig":{"access":"public"},"engines":{"node":">=18.0.0"},"homepage":"https://github.com/ClickerMonkey/aeye#readme","bugs":{"url":"https://github.com/ClickerMonkey/aeye/issues"},"_id":"@aeye/ginny@0.3.9","gitHead":"7df3bae899c5a30cf17f4925fb7057e9a61e7332","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-V5lyk5CxurThOffT+qSdF6xyX04rF/QfLPlRm+hBG5Btr1wHwQFkaVcUSsxtQSWouLvf42IHtBIuPZvFungWAA==","shasum":"aba83d73f48a53c74c1f225e4be6a455e8e0ad22","tarball":"https://registry.npmjs.org/@aeye/ginny/-/ginny-0.3.9.tgz","fileCount":8,"unpackedSize":25070845,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEIlURSoqlmfoB2mHJ8Oui63PC/nQP1D3AgTiWQHcrvuAiAMb3oZTicLjhNLAtrVyrP5UnvVg8liSieRMrPZ3nVp4Q=="}]},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"directories":{},"maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ginny_0.3.9_1783858115841_0.08854204076345296"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-12T12:08:35.707Z","0.3.9":"2026-07-12T12:08:36.253Z","modified":"2026-07-12T12:08:36.455Z"},"maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"description":"Ginny — CLI that turns natural-language requests into executable gin programs","homepage":"https://github.com/ClickerMonkey/aeye#readme","keywords":["cli","ai","gin","agent","code-generation"],"repository":{"type":"git","url":"git+https://github.com/ClickerMonkey/aeye.git","directory":"packages/ginny"},"author":{"name":"ClickerMonkey"},"bugs":{"url":"https://github.com/ClickerMonkey/aeye/issues"},"license":"GPL-3.0","readme":"# ginny\n\n> A CLI that turns natural-language requests into executable\n> [gin](../gin) programs. Types, functions, and vars accumulate across\n> sessions as JSON on disk — the LLM builds a living catalog of\n> reusable code that grows with your project.\n\n```bash\nnpm install -g @aeye/ginny\ncd my-project\nginny                       # opens an interactive REPL\nginny \"add 2 and 3\"         # one-shot\n```\n\n## What ginny does\n\nYou describe what you want. A **programmer** sub-agent writes a gin\nprogram to do it, tests it against sample inputs, and returns the\nresult. If it needs types, reusable functions, or named vars, it asks\nspecialist sub-agents that search the local catalog and create new\nentries when nothing matches.\n\nEverything is typed end-to-end. Every write/test/finish cycle happens\ninside gin's type system — the agent can't produce invalid expressions,\nand the structured output you get back carries full type information.\n\nFor complex requests (multiple types and functions, ambiguous scope,\n\"build me a small system…\") the programmer pauses to ask clarifying\nquestions, writes a plan listing the types/fns/vars it intends to\ncreate, and waits for your approval before any code is written. Simple\nrequests like \"add 2 and 3\" skip the dance.\n\n## First run\n\n```bash\n$ cd my-new-project\n$ ginny\n\nCreated /path/to/my-new-project/config.json\nAdded config.json + ginny.log to .gitignore\n\nPopulate the file before re-running:\n  At least one AI provider:\n    - OPENAI_API_KEY (openai)\n    - OPENROUTER_API_KEY (openrouter)\n    - AWS Bedrock — any valid AWS credential source works (env vars,\n      `aws sso login`, IAM role, ~/.aws/credentials, etc.). Ginny\n      probes the credential chain at startup; AWS_REGION optional.\n  TAVILY_API_KEY — optional, enables web_search tool\n  GIN_PROVIDER — optional, preferred provider (openai | openrouter | aws)\n  GIN_MODEL — optional, specific model id\n  GIN_SEARCH_THRESHOLD — optional, corpus size below which search returns all (default 20)\n  GIN_TOOL_ITERATIONS — optional, max tool-call iterations per prompt run (default 100)\n\nEnvironment variables still win over config.json values.\n```\n\nEdit `config.json`, set at least one provider key, and re-run:\n\n```json\n{\n  \"OPENAI_API_KEY\": \"sk-...\",\n  \"GIN_PROVIDER\": \"openai\",\n  \"GIN_MODEL\": \"gpt-4o-mini\",\n  \"TAVILY_API_KEY\": \"tvly-...\"\n}\n```\n\n## Architecture\n\nginny is a small council of sub-agents, each specialized:\n\n```\n                          ┌─────────────┐\n       user request  ──▶  │  programmer │\n                          └──────┬──────┘\n         ┌────────────────┬──────┴──────┬────────────────┐\n         ▼                ▼             ▼                ▼\n  ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐\n  │  architect  │ │   designer   │ │     dba      │ │  researcher  │\n  │   (types)   │ │    (fns)     │ │    (vars)    │ │  (web search │\n  │             │ │              │ │              │ │   + pages)   │\n  └─────────────┘ └──────┬───────┘ └──────────────┘ └──────────────┘\n                         │\n                         ▼ (recursive spin-up)\n                    programmer\n```\n\n- **programmer** — writes a gin `ExprDef`, calls `test()` against it,\n  and `finish()` when a test passes. Has the build tools\n  (`write` / `test` / `finish`), the find-or-create tools for pulling\n  in catalog items, an `edit_type` tool for backwards-compatible type\n  edits, and a `research` tool for factual lookups.\n- **architect** — searches `./types/*.json` by keyword (top-N when the\n  catalog grows past the threshold, or all entries below); returns\n  existing types or designs new ones.\n- **designer** — same pattern over `./fns/*.json`. Has both\n  `create_new_fn` (new function from scratch — recursively spawns a\n  programmer to author the body) and `edit_fn` (backwards-compatible\n  signature change + fresh body). The compat checker accepts widening\n  edits and rejects breaking ones.\n- **dba** — same pattern over `./vars/*.json` (typed named values the\n  user or agent can read/write).\n- **researcher** — wraps `web_search` + `web_get_page`; answers a\n  natural-language question iteratively and returns\n  `{ answer, sources }`.\n\n## Persistence\n\nEvery catalog entry is one JSON file per name. The filename IS the\nidentity. The three directories are relative to your current working\ndirectory:\n\n```\n./types/Task.json           # the Task type\n./fns/factorial.json        # the factorial function\n./vars/apiBaseUrl.json      # a persistent var (type + value + docs)\n```\n\nYou can hand-edit any of these between sessions. The next run picks up\nyour changes. Drop a new file into any of the three directories by\nhand and ginny discovers it on the next search.\n\n### Example: `./vars/apiBaseUrl.json`\n\nA var is a `{type, value, docs}` triple — the simplest on-disk shape:\n\n```json\n{\n  \"type\":  { \"name\": \"text\", \"options\": { \"pattern\": \"^https?://\" } },\n  \"value\": \"https://api.example.com\",\n  \"docs\":  \"production API root\"\n}\n```\n\nLoaded at use time, `vars.apiBaseUrl` shows up in scope as a typed\n`text` value that any program can read.\n\n### Types and functions\n\n`./types/<Name>.json` is a `TypeDef` — gin's serialized type\ndescriptor. `./fns/<name>.json` is a `function`-typed `TypeDef` whose\nbody lives at `call.get` (gin's native callable shape — see\n[gin/src/path.ts](../gin/src/path.ts) for how the path walker\ndispatches). The top-level `docs` field is the function's description.\n\nSee the [gin README](../gin#core-concepts) for what TypeDef and ExprDef\nlook like.\n\n## Built-in globals\n\nPrograms always have access to:\n\n- **`fns.fetch<R: any>({ url, method?, headers?, body?, output?: typ<R> }): R`**\n  HTTP fetch. When `output` is a gin Type, the response body is parsed\n  through it — type-safe HTTP in one call.\n\n- **`fns.llm<R: text | obj>({ prompt, tools?, output?: typ<R> }): R`**\n  LLM invocation. Pass a gin Type as `output` to get structured, typed\n  output. The `<R: text | obj>` constraint says R must be either a\n  text-shaped reply or an obj-shaped structured output — chosen at the\n  call site.\n\n- **`fns.log({ message: any }): void`**\n  Print a runtime message to the user (stderr). Use for progress\n  narration, intermediate values, or debug breadcrumbs. Distinct from\n  the program's return value.\n\n- **`fns.ask<R: any>({ title: text, details: text, output?: typ<R> }): optional<R>`**\n  Pause the program and prompt the user. With `output` set the\n  consumer walks the user through any complex shape (obj fields, list\n  items, choices, optionals) — every (sub)type's `docs` field becomes\n  the user-facing label. Returns `null` (`optional<R>`) if the user\n  cancels, so the program must handle that branch explicitly.\n\n- **`vars.<name>`** — any var you've created or imported.\n\n## The write / test / finish loop\n\n```\n> compute the factorial of 6\n\n• (programmer calls find_or_create_functions \"factorial function\")\n• (designer spins up a fresh programmer → writes the recursive gin program)\n• (programmer calls write(program))\n• (programmer calls test() → SUCCESS: 720)\n• (programmer calls finish())\n\n720\n```\n\n`test()` runs the draft program against sample args. The programmer\ncan set `expectError: true` to verify a program raises — useful for\n\"divide 1 by 0 and tell me what happens\".\n\n`finish()` accepts an optional `saveAs: '<camelCaseName>'` to persist\nthe program as a reusable function — every saved fn becomes a\ncallable global, so subsequent runs can invoke it directly.\n\n## Editing existing types and functions\n\nTwo tools cover backwards-compatible edits:\n\n- **`edit_type({ name, def })`** (programmer) — replace a saved type's\n  definition. Allowed: add OPTIONAL fields, widen existing field\n  types, loosen constraints. Rejected: remove fields, add required\n  fields, narrow field types, change the type class.\n- **`edit_fn({ name, args, returns, body })`** (designer) — change a\n  saved function's signature and body. Args may add optional params\n  or widen existing param types; returns may NARROW. The body is\n  rewritten from scratch by an inner programmer.\n\nBoth tools enforce backwards-compat at parse time and reject breaking\nchanges with a structured error. If a change is genuinely incompatible\nthe right move is usually to create a new type / fn under a different\nname so existing callers keep working.\n\n## Configuration\n\nConfig values can come from `config.json` in the current working\ndirectory, or from environment variables (env wins on conflict):\n\n| Key | Purpose |\n|---|---|\n| `OPENAI_API_KEY` | enables OpenAI provider |\n| `OPENROUTER_API_KEY` | enables OpenRouter provider |\n| `AWS_REGION` | region for AWS Bedrock (default `us-east-1`) |\n| `TAVILY_API_KEY` | enables the `web_search` tool |\n| `GIN_PROVIDER` | preferred provider (openai \\| openrouter \\| aws) |\n| `GIN_MODEL` | pin a specific model id (fallback for any sub-agent without an override) |\n| `GIN_PROGRAMMER_MODEL` | model id for the programmer sub-agent |\n| `GIN_DESIGNER_MODEL` | model id for the designer (fns) sub-agent |\n| `GIN_ARCHITECT_MODEL` | model id for the architect (types) sub-agent |\n| `GIN_DBA_MODEL` | model id for the dba (vars) sub-agent |\n| `GIN_RESEARCHER_MODEL` | model id for the researcher sub-agent |\n| `GIN_LLM_MODEL` | model id for the in-program `fns.llm` calls |\n| `GIN_SEARCH_THRESHOLD` | corpus size below which catalog search returns all entries (default 20) |\n| `GIN_TOOL_ITERATIONS` | max tool-call iterations per prompt run (default 100) |\n\n### AWS Bedrock\n\nAWS isn't behind a single env var. Ginny probes the AWS SDK's standard\ncredential chain at startup — if it can call `ListFoundationModels`,\nBedrock is added as a provider. That means **any** of these work\nwithout extra config:\n\n- `aws sso login` (SSO profile active in the current shell)\n- `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` env vars\n- IAM role attached to the EC2/ECS/Lambda/etc. instance\n- `~/.aws/credentials` with a default profile\n- Container credential provider\n\nAt startup ginny prints a line like:\n\n```\nginny: providers enabled → openai, aws + web_search (tavily)\n       skipped → openrouter (OPENROUTER_API_KEY unset)\n```\n\nAt least one provider must resolve. Tavily is optional — without it\nthe programmer still has `web_get_page` (fetch + strip HTML).\n\n## Logging\n\nEach session writes a verbose timeline to `./ginny.log` (truncated on\nstartup). Tool inputs and outputs, full validation problems, full\nzod parse errors, and stack traces all land in the log; the terminal\nview stays compact (one line per error, capped at 200–4096 chars\ndepending on context). When something goes sideways, `ginny.log` is\nwhere to look.\n\n## Example sessions\n\n```\n> fetch the title of example.com\n  → reads web_get_page, extracts <title>, returns the text.\n\n> remember my api base url is https://api.example.com as 'apiBaseUrl'\n  → vars manager creates ./vars/apiBaseUrl.json\n\n> print my api base url\n  → programmer reads vars.apiBaseUrl, returns the string.\n\n> define a Task type with title, done, due\n  → architect creates ./types/Task.json (extending obj with props).\n\n> create a program that counts done tasks from a list of tasks\n  → programmer emits a list.filter + .length program using Task.\n\n> add an `assignee` field to Task (optional)\n  → programmer calls edit_type — backwards-compatible widening accepted.\n```\n\n## Building from source\n\n```bash\ngit clone https://github.com/ClickerMonkey/aeye.git\ncd aeye\nnpm install\ncd packages/ginny\nnpm run start              # dev (tsx --conditions=source)\nnpm run build              # bundled dist/index.js with shebang\n```\n\nThe production build is a single ESM file with a Node shebang — the\nglobal install (`npm i -g @aeye/ginny`) links `ginny` straight to it.\n\n## How it relates to gin\n\nginny is an application built on top of [`@aeye/gin`](../gin). gin\nprovides:\n\n- the type system (`num`, `list<V>`, `typ<T>`, user extensions, ...)\n- the expression engine (`engine.run(expr)`)\n- the Zod schema generation the LLM uses to author valid programs\n\nginny provides:\n\n- the AI wiring (provider selection, model override, per-request context)\n- the sub-agent orchestration (architect / designer / dba / researcher / programmer)\n- the CWD-relative catalog (types / fns / vars directories)\n- the REPL and one-shot CLI entry point\n\nIf you want to embed the same capabilities in your own application\nrather than use a CLI, use `@aeye/gin` directly — everything ginny\ndoes is a thin layer of tool definitions over gin's public API.\n\n## License\n\nGPL-3.0\n","readmeFilename":"README.md","_rev":"1-4c896156af697127ec02342e4a48aaec"}