{
    "archive_path": "archive/1792879566.16204",
    "base_url": "github.com/gtanczyk/genaicode",
    "basename": "genaicode",
    "bookmarked_date": "2026-10-24 22:06",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/github.com/gtanczyk/genaicode",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=github.com",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": "github.com/gtanczyk/genaicode.html"
    },
    "domain": "github.com",
    "extension": "",
    "hash": "MGCWX467RQGGQNWCDWJ3",
    "history": {
        "archive_org": [],
        "dom": [],
        "favicon": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://www.google.com/s2/favicons?domain=github.com"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-10-24T22:10:34.251397+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1792879566.16204",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-24T22:10:33.801781+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--head",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://github.com/gtanczyk/genaicode"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-10-24T22:10:34.881717+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1792879566.16204",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-24T22:10:34.258967+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [],
        "media": [],
        "mercury": [
            {
                "cmd": [
                    "/app/node_modules/@postlight/parser/cli.js",
                    "https://github.com/gtanczyk/genaicode",
                    "--format=text"
                ],
                "cmd_version": "1.0.0",
                "end_ts": "2026-10-24T22:10:58.240392+00:00",
                "index_texts": null,
                "output": "ArchiveError: Got /app/node_modules/@postlight/parser/cli.js response code: 1).",
                "pwd": "/data/archive/1792879566.16204",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-24T22:10:51.900684+00:00",
                "status": "failed"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/app/node_modules/readability-extractor/readability-extractor",
                    "/tmp/tmp8w8svjlu",
                    "https://github.com/gtanczyk/genaicode"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-10-24T22:10:51.889875+00:00",
                "index_texts": [
                    "GenAIcode\nGenAIcode drives the coding-agent CLIs you already have (Claude Code, Codex, GitHub Copilot\nCLI, Cursor, Gemini CLI, opencode, Muse, Mistral Vibe, Antigravity) behind one API and one\nCLI. Script them, swap them with one flag, and let your tests decide when the job is done.\n\n  \n    \n  \n  \n  Recorded live with Claude Code and Codex. Full video (87 s).\n\nnpx genaicode run --verify \"npm test\" \"fix the failing tests\"\nThat runs the first agent CLI installed on your machine in the current directory, then runs\nnpm test. If the check fails, its output goes back to the agent for another attempt. Add\n-a codex (or claude, copilot, opencode...) to pick the agent; the command stays the\nsame. From there, the same drivers are a TypeScript API, a chat in your terminal or browser,\na panel inside your Vite app, and an agent inside your own app.\nGenAIcode also ships a thin LLM layer for calling model providers\nfrom backend code. The two halves are separate subpaths: importing genaicode never spawns an\nagent, and genaicode/agents loads no provider SDK.\nInstall\n\nNode.js 20 or newer is required. The agent CLIs are yours to install and log in to;\nnpx genaicode agents lists which ones it finds.\nCoding agents\ngenaicode/agents turns the coding-agent CLIs installed on a machine into one API. Give\nany of them a task (a prompt and a working directory) and get back the same event stream\nand result, whichever agent does the work. It is how you script, compare, or chain coding\nagents from a job, a bot, or a CI step without writing a parser per vendor.\nimport { claude, codex, copilot, cursor, detectAgents } from 'genaicode/agents';\n\nconst [found] = detectAgents([claude(), codex(), copilot(), cursor()]).filter(({ path }) => path);\nif (!found) throw new Error('No coding agent installed.');\n\nconst run = found.agent.run({ prompt: 'Add a unit test for parseDate', cwd: '/path/to/repo', timeoutMs: 20 * 60_000 });\nfor await (const event of run) {\n  if (event.type === 'tool-start') console.log('\u2699', event.name);\n  if (event.type === 'file-change') console.log('edited', event.paths.join(', '));\n}\nconst result = await run.result; // { status, ok, text, sessionId, usage, error, ... }\n\n\n\nAgent\nDriver\nExtras\n\n\n\n\nClaude Code\nclaude()\nMCP servers, approvals, max turns, effort, cost\n\n\nCodex\ncodex(), codexLive()\nMCP servers; live: steer(), approvals\n\n\nGitHub Copilot\ncopilot()\nMCP servers, allow/deny tool patterns\n\n\nCursor\ncursor()\nstreamed text deltas\n\n\nGemini CLI\ngemini()\napproval mode\n\n\nopencode\nopencode()\nprovider/model ids, variants\n\n\nMistral Vibe\nvibe()\nagent profile, max turns, max price\n\n\nAntigravity CLI\nantigravity()\nsandbox, effort, usage\n\n\nMuse\nmuse(), museLive()\nmax turns; live: steer(), approvals\n\n\nYour own CLI/API\ncliAgent(), hostedAgent()\nplug in any JSON-lines CLI or hosted agent\n\n\n\nOn top of the drivers:\n\nSteer a running task with run.steer(text) (live drivers), and answer permission\nprompts with onApproval (Claude, live Codex and Muse).\nPick approval and sandbox policy with permissions: { approval, sandbox } (or\n'yolo'); each driver maps it to the agent's own flags and refuses what it cannot honor.\nAttach MCP servers per task with mcpServers. Header secrets stay out of argv.\nVerify and repair: runWithVerify runs your check (tests, lint) after the agent and\nsends failures back for another attempt.\nKeep credentials apart: withoutProviderCredentials() strips your app's LLM API keys\nfrom the agent's environment, so it bills its own login.\n\nImporting genaicode never spawns anything; only genaicode/agents does. The agent uses\nits own credentials and billing, and its own permission settings unless permissions sets\nthem; any sandbox is the agent's own. GenAIcode does not choose a model or retry it. See docs/agents.md for events, options per\ndriver, and writing your own driver.\nFrom the command line\nThe genaicode bin runs the same headless drivers without writing any code:\nnpx genaicode agents                                   # which agent CLIs are installed\nnpx genaicode run \"Add a unit test for parseDate\"      # first installed agent, current dir\nnpx genaicode run -a codex -C ../api --verify \"npm test\" \"Fix the failing date tests\"\ngit diff | npx genaicode run --json -a claude -        # prompt from stdin, JSON-lines events\nAssistant text goes to stdout and tool calls, edits, and the result line go to stderr.\n--verify runs a shell command after the agent finishes and sends its output back for up\nto --max-repairs more attempts (runWithVerify). The exit code is 0 on success, 1 when\nthe agent or the check fails, 2 on a usage error, and 130 when interrupted.\nnpx genaicode --help lists every option.\nChat with an agent: terminal and browser\nnpx genaicode                      # chat in this terminal (same as \"genaicode chat\")\nnpx genaicode chat -a claude -C ../api\nnpx genaicode ui                   # the same chat in your browser, served on 127.0.0.1\nnpx genaicode chat --web           # both: the terminal and the browser on one session\nThe terminal and the browser can show one session. /web in the terminal chat opens it in\nthe browser (/web off stops the server), and Enter in the terminal that runs\ngenaicode ui brings it into that terminal. A prompt, an approval or a stop from either\nside shows up in both.\nEach prompt after the first continues the agent's own session (AgentTask.resume), so you\ncan say \"now add a test for that\". Prompts sent while the agent works wait in a queue and\nrun next; Esc stops the agent. When the agent asks for permission, y approves, n\ndenies and a approves everything from then on (/auto, or the Auto-approve chip in the\nbrowser; -y starts that way). /agent, /model and /new switch agent, model and\nsession; /help lists the rest.\n\n  \n    \n    \n  \n  \n    \n    \n    \n    \n    \n  \n\nThe wolf from genaicode 1.0 is back. When the agent asks for approval or finishes, the\nbrowser UI barks if its tab is in the background and counts those events in the tab title.\nThe terminal UI rings the terminal bell instead. /bark off (or the Bark button) quiets it.\n\n  \n    \n    \n    \n  \n\ngenaicode ui listens on the loopback address and prints a link with a random access\ntoken. The page, the event stream and every command need that token; only the bundled\nscript and the four mascot files (/app.js, /assets/\u2026) are served without it, since they\nhold nothing about your session. Both front ends are bundled into the package and load\nonly for these commands; importing genaicode or genaicode/agents pulls in no UI code.\nIn code, the same conversation is createAgentSession from genaicode/agents (see\ndocs/agents.md); with a live driver (codexLive,\nmuseLive) it also steers the running agent and asks you about permission requests.\nLet your agent embed it\nnpx genaicode embed                      # in your project: your agent wires genaicode in\nnpx genaicode embed -a codex \"only the dev panel\"\nnpx genaicode embed --print | pbcopy     # just the instructions, for any other agent\ngenaicode embed does not inspect your project itself. It starts your agent with\ninstructions on how to embed genaicode, and the agent reads the project and picks the way\nin: the Vite overlay below for an app on Vite; for any other Node dev server (webpack,\nNext.js, Express...) the same overlay through createDevOverlay from genaicode/ui; for\npages with no dev server at all, a small dev-only server of its own with that overlay; the in-app agent (genaicode/app-agent with\n<AppAgentChat>) when the agent should act on the app itself; and npx genaicode ui next\nto the dev server when there is no Node process to host it. Words after embed go to the\nagent as your request. You watch and answer in the same chat as genaicode chat (--ui\nfor the browser); without a terminal it runs headless like run. The agent installs the\npackage, edits the config, runs your checks and reports; it does not commit.\nThe instructions are also a Claude Code skill. Copy\nskills/genaicode-embed into .claude/skills/ (or\n~/.claude/skills/) and ask Claude to \"embed genaicode\":\nmkdir -p .claude/skills && cp -r node_modules/genaicode/skills/genaicode-embed .claude/skills/\nInside a Vite app\n// vite.config.ts\nimport { defineConfig } from 'vite';\nimport genaicode from 'genaicode/vite';\n\nexport default defineConfig({ plugins: [genaicode()] });\n\n  \n    \n    \n    \n  \n\nIn vite dev, the app gets the wolf in its corner. It opens the same chat as genaicode ui\nin a panel, with the agent working in the project root. The wolf counts the page's errors\n(build errors, uncaught exceptions, unhandled rejections, console.error), and \"Fix N\nerrors\" sends them to the agent as one prompt. Options: agent, model, effort,\napproveAll, agents, port, and captureErrors: false to leave the page's errors alone.\nBuilds are untouched. The chat runs on its own port on 127.0.0.1 even when Vite runs with\n--host, lets only the app's origins frame it, and hands its token (and takes \"fix\" requests)\nonly from the app's own pages on this machine.\nAny other Node dev server (Express, webpack-dev-server, a script that serves static files)\ngets the same overlay from createDevOverlay in genaicode/ui:\nimport { createDevOverlay } from 'genaicode/ui';\n\nconst overlay = createDevOverlay(); // same options as the plugin, plus `cwd`\napp.use(overlay.middleware); // Express; or `if (await overlay.handle(req, res)) return;`\n// ...and in the HTML it serves: overlay.injectHtml(html), or the overlay.script tag.\nDevelopment only. The token goes only to pages opened at localhost, *.localhost,\n127.0.0.1 or [::1], and without an agent CLI the overlay stays off.\nIn your own React app\ngenaicode/react is the same chat as a component. It draws a SessionState your app gives\nit and calls you back; it runs, fetches and stores nothing, so your server owns the session,\nits MCP servers and its prompts. React 18 or newer comes from your app: genaicode does not\ndeclare or install it.\n// server (Node): one session, streamed to the page\nimport { createAgentSession, claude } from 'genaicode/agents';\n\nconst session = createAgentSession({\n  agent: claude(),\n  cwd: process.cwd(),\n  task: { mcpServers: [{ name: 'ops', url: 'http://127.0.0.1:4000/mcp' }] },\n  // The agent gets the context; the transcript keeps what the user typed.\n  transformPrompt: (text) => `${briefing}\\n\\nOperator message:\\n${text}`,\n});\n\napp.get('/api/agent/events', (req, res) => {\n  res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store' });\n  const push = (state) => res.write(`data: ${JSON.stringify(state)}\\n\\n`);\n  push(session.get());\n  res.on('close', session.subscribe(push));\n});\napp.post('/api/agent/send', (req, res) => {\n  session.send(req.body.text);\n  res.json({ ok: true });\n});\napp.post('/api/agent/stop', (req, res) => {\n  session.stop();\n  res.json({ ok: true });\n});\napp.post('/api/agent/approve', (req, res) => res.json({ ok: session.approve(req.body.id, req.body.decision) }));\n// page\nimport { useEffect, useState } from 'react';\nimport { AgentChat, type SessionState } from 'genaicode/react';\nimport 'genaicode/react/styles.css';\n\nconst post = (path: string, body: object = {}) =>\n  fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n\nexport function AgentDrawer() {\n  const [state, setState] = useState<SessionState | null>(null);\n  useEffect(() => {\n    const events = new EventSource('/api/agent/events');\n    events.onmessage = (event) => setState(JSON.parse(event.data));\n    return () => events.close();\n  }, []);\n  return (\n    <AgentChat\n      state={state}\n      onSend={(text) => post('/api/agent/send', { text })}\n      onStop={() => post('/api/agent/stop')}\n      onApprove={(id, decision) => post('/api/agent/approve', { id, decision })}\n      formatToolName={(name) => name.replace(/^mcp__ops__/, '')}\n      renderFooter={() => <MyApprovalCards />}\n      headerExtras={<button onClick={() => post('/api/agent/reset')}>New</button>}\n    />\n  );\n}\n<AgentChat> fills its container's height. It shows turns with streaming Markdown (no raw\nHTML; links open in a new tab), collapsible tool calls, edited files, errors, the agent's\nown approval requests, queued prompts and a working indicator, and an input where Enter\nsends, Shift+Enter adds a line and Esc stops. Props:\n\n\n\nProp\n\n\n\n\n\nstate\nSessionState, or null before there is a session\n\n\nonSend(text), onStop()\nEnter or Send; Stop or Esc. No onStop, no Stop button\n\n\nonApprove(id, decision)\nanswers state.approvals; without it they show as waiting\n\n\nrenderPrompt(text, turn?)\nhow a sent prompt shows (return null to hide it)\n\n\nrenderAfterTurn(turn, i), renderFooter()\nyour content after a turn, or at the end\n\n\nnotices\n{ id, afterTurn, text, tone? }[]: your own lines between turns\n\n\nfoldSteps\nfold each turn's tools and edits into one \"N steps\" line\n\n\nheader, headerExtras\nreplace the header (null for none), or add to its right end\n\n\nemptyState, placeholder, hint\ntext before the first turn, in and under the input\n\n\nformatToolName(name), disabled, autoFocus, theme, className, style\n\n\n\n\nTheme it with CSS custom properties on the component or any ancestor: --gc-bg,\n--gc-panel, --gc-border, --gc-text, --gc-muted, --gc-accent, --gc-accent-text,\n--gc-error, --gc-font, --gc-mono, --gc-font-size, --gc-radius, --gc-max-width\n(and --gc-ok, --gc-warn, plus soft shades mixed from these unless set). Unset ones keep\nthe genaicode ui look, light or dark by prefers-color-scheme, or by theme=\"light\" /\n\"dark\". genaicode ui and the Vite panel draw this same component.\nTwo smaller pieces come along for hosts that build their own panels around a transcript:\nimport { installResizeHandle, storedSize, useFollowScroll } from 'genaicode/react';\n\n// A log that stays at its newest line until the user scrolls up (and again after follow()).\nconst { ref, onScroll, follow } = useFollowScroll(lines);\n\n// A drag handle on a panel's left edge; the width survives reloads.\nconst stored = storedSize('panel-width', [300, 900]);\nlet width = stored.get();\nconst handle = installResizeHandle(edge, {\n  axis: 'x',\n  direction: () => -1, // dragging left makes the panel wider\n  bounds: () => [300, Math.min(900, innerWidth - 24)],\n  read: () => width ?? 390,\n  write: (next, commit) => {\n    width = next;\n    panel.style.setProperty('--width', `${width}px`);\n    if (commit) stored.set(width);\n  },\n  reset: () => {\n    width = undefined;\n    panel.style.removeProperty('--width');\n    stored.clear();\n  },\n});\n// The CSS caps the shown width, so the saved one comes back when the window grows again:\n// .panel { width: min(var(--width, 390px), calc(100vw - 24px)); }\nwindow.addEventListener('resize', handle.sync);\nThe handle keeps its aria-value* range current and takes the arrow keys (Shift for bigger\nsteps), Home and End, Escape to undo a drag, and Enter or double-click to reset. While a drag\nruns, document.body (or root) has data-resizing=\"true\", so the page can keep the cursor\nand turn off pointer events on iframes. read() must return what write or reset just set,\nso keep the size in a variable or a ref rather than only in React state.\nTo show the whole genaicode ui page in a frame instead, startEmbeddedWeb from\ngenaicode/ui serves it on 127.0.0.1 with your task (mcpServers, timeoutMs...), env\nand transformPrompt, and returns its tokenized url and a controller to send prompts.\nInside your own local app\ngenaicode/app-agent puts an agent in a local app you run on your machine (an admin\nconsole, an internal tool, a game editor). The user chats with an agent CLI they already\nhave; the agent works the app through tools the app serves over MCP, changes wait for the\nuser's approval, and in dev mode the agent also edits the app's code and proposes a commit.\nimport { createServer } from 'node:http';\nimport { createAppAgent, createAppAgentHandler } from 'genaicode/app-agent';\n\nconst agent = createAppAgent({\n  name: 'the shop admin',\n  mcpUrl: 'http://127.0.0.1:3000/api/agent/mcp',\n  tools: [\n    { name: 'orders', description: 'List open orders.', run: () => JSON.stringify(listOrders()) },\n    {\n      name: 'refund',\n      description: 'Refund an order.',\n      write: true, // a card with Approve and Reject; runs only after Approve\n      inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },\n      run: ({ id }) => refund(String(id)),\n    },\n  ],\n  pages: () => ['/orders', '/customers'], // page context, link chips, \"take me there\"\n  dev: { cwd: process.cwd(), commands: ['npm test'] }, // optional: the agent edits the app\n});\nconst handle = createAppAgentHandler(agent, { authorize: (req) => isSignedIn(req) });\n\ncreateServer(async (req, res) => {\n  if (await handle(req, res)) return;\n  // ...the rest of the app\n}).listen(3000, '127.0.0.1');\nOn the page, AppAgentChat from genaicode/react is the whole drawer: the chat, the agent's\npage chips under its replies, Approve and Reject cards (with the diff of a proposed commit),\nand the agent and mode pickers. It reports currentPath to the agent and calls onNavigate\nwhen a chip is clicked or the agent is asked to go somewhere.\nimport { AppAgentChat } from 'genaicode/react';\nimport 'genaicode/react/styles.css';\n\n<AppAgentChat currentPath={location.pathname} onNavigate={(path) => router.push(path)} />;\n\n  \n\nUnder it, /api/agent serves GET /events (a stream of AppAgentState: the session, earlier\nturns, pending actions, link chips, navigation requests) and POST /send, /stop, /reset,\n/config, /view, /actions/:id and /approvals/:id (the agent CLI's own permission\nprompts); useAppAgent wraps them for a page of your own. Switching agent or mode keeps the\nconversation: the next agent gets a short transcript. trace receives every prompt, MCP call,\ndecision and finished turn, for a log. Default agents are Claude Code, Codex and\nCopilot CLI, locked down per mode: in use mode they get only the app's tools (no shell, no\nfile edits); in dev mode they edit files and run the listed commands, never commit or push (Claude and\nCopilot enforce the command list; Codex has no per-command allowlist, so it runs in its\nworkspace-write sandbox: writes only inside cwd, no network).\npropose_commit snapshots the worktree, the user reviews that diff, and Approve commits\nexactly that tree on the reviewed HEAD, or refuses if the files or HEAD changed since, a hook\nchanged the files, or a merge or rebase is in progress. The repository's commit hooks run.\nThe agent sees no MCP server but the app's: Claude runs with --strict-mcp-config, and Codex\nand Copilot get the user's own configured servers switched off. The MCP endpoint takes a per-run\nbearer and refuses browser requests; every other route asks authorize and refuses\ncross-site requests. Without authorize only a loopback Host is accepted (add names with\nallowedHosts), so a DNS-rebinding page cannot reach the agent.\nAlso: a thin LLM layer\nBesides driving agents, GenAIcode is a small TypeScript toolkit for calling LLMs from backend\ncode. It sits between raw provider SDKs and full agent frameworks: one prompt representation,\nthin provider adapters, a convenient request API, and lightweight conversation chains. This\nlayer does not inspect repositories, execute shell commands, edit files, or run an agent UI.\nLike jQuery, the common case starts with one small function and becomes more specific\nthrough chaining\u2014configure a request, follow up across multiple prompts, and keep history\nportable without adopting a full agent framework.\nA prompt in three lines\nimport { genaicode } from 'genaicode';\nimport { openai } from 'genaicode/providers';\n\nconst ai = genaicode(openai({ model: 'your-model-name' }));\nconst answer = await ai('Explain why the sky is blue in two sentences.').text();\nThe client is callable on purpose. Configuration is ordinary method chaining\u2014system\ninstructions, temperature, token limits, and more\u2014without a separate options object or\nframework setup:\nconst result = await ai('Create a release note from these commits')\n  .system('You are a concise technical writer.')\n  .temperature(0.2)\n  .maxOutputTokens(500)\n  .text();\nBuilders are immutable, so a configured base request can be safely reused:\nconst releaseNote = ai('Create a release note from these commits')\n  .system('You are a concise technical writer.')\n  .temperature(0.2);\n\nconst short = await releaseNote.maxOutputTokens(200).text();\nconst long = await releaseNote.maxOutputTokens(800).text();\nWhy this vs a raw SDK\nProvider SDKs are the right tool when you call one vendor and own every request shape\nyourself. The cost shows up when the same backend needs a second provider, a test double,\nor shared policy (timeouts, JSON parsing, rate limits, fallback) without copying glue.\nGenAIcode is that thin shared layer:\n\nOne portable prompt/tool IR (PromptItem) instead of OpenAI messages vs Anthropic\nblocks vs Gemini contents.\nOne request API (.text(), .json(), .stream(), chains) over those adapters.\nA tiny ModelProvider seam so tests and custom gateways do not mock vendor HTTP.\nOpt-in middleware and retry helpers\u2014no hidden tool runners, no silent retries, no\nagent loop.\n\nIf you are happy importing one SDK and never swapping models or providers, stay on the\nSDK. If you want the call site to look the same while the edge stays replaceable, use\nGenAIcode.\nChaining prompts\nA chain remembers successful user and assistant turns. Each new prompt sees the complete\nhistory, so multi-step work stays in ordinary application code:\nimport { system } from 'genaicode';\n\nconst chain = ai.chain(system('You are helping refine an API design.'));\n\nconst answer1 = await chain.text('Prompt 1: propose a minimal endpoint.');\nconst answer2 = await chain.text('Prompt 2: add idempotency to that design.');\nconst answer3 = await chain.text('Prompt 3: summarize the final contract.');\nThis is prompt1 \u2192 response1 \u2192 prompt2 \u2192 response2 \u2192 prompt3 \u2192 response3. The history\nis available as portable PromptItem[] through chain.history().\nUsing a chain in a loop\nA chain is ordinary application state, so normal control flow works:\nconst chain = ai.chain(system('Improve the draft while preserving its meaning.'));\nlet draft = 'The deployment had a problem and we fixed it.';\n\nfor (const instruction of ['Make it specific.', 'Make it concise.', 'Use a professional tone.']) {\n  draft = await chain.text(`${instruction}\\n\\nCurrent draft:\\n${draft}`);\n}\nCalls on one chain are serialized, even if application code starts them concurrently.\nFailed provider calls are not added to history.\nValidation and repair are also plain loops rather than a special agent abstraction:\nimport { parseJsonResult, resultText, system } from 'genaicode';\n\nconst repair = ai.chain(system('Return only valid JSON with a non-negative count.'));\nlet count: number | undefined;\n\nfor (let attempt = 1; attempt <= 3; attempt += 1) {\n  const result = await repair.ask(\n    attempt === 1 ? 'Count the actionable items in this text: ...' : 'Correct the previous response.',\n  );\n\n  try {\n    const value = parseJsonResult<{ count: number }>(result);\n    if (value.count < 0) throw new Error('count must be non-negative');\n    count = value.count;\n    break;\n  } catch (error) {\n    if (attempt === 3) throw error;\n    console.warn('Invalid model response:', resultText(result));\n  }\n}\nThe application owns validation, attempt limits, and failure policy.\nSchema adapters\njson(...) and parseJsonResult(...) accept either a parser function or a schema adapter\nwith a parse(value) method. This keeps schema validation library-agnostic:\nconst value = await ai('Return {\"count\": 3}.').json({\n  parse(input) {\n    if (typeof input !== 'object' || input === null || typeof input.count !== 'number') {\n      throw new Error('Invalid shape');\n    }\n    return input;\n  },\n});\nCalling .json() also sets responseFormat: { type: 'json' } on the request when you have\nnot already chosen a format, so providers that support JSON mode (OpenAI, Gemini/Vertex)\nare asked for JSON rather than free text.\nResponse format and thinking\nPortable request fields cover the two knobs backends usually poke through provider-specific\nconfig:\nconst verdict = await ai(promptText)\n  .responseFormat({ type: 'json' })\n  .thinking({ level: 'minimal' }) // or { budgetTokens: 0 } / false to disable\n  .temperature(0)\n  .json((value) => VerdictSchema.parse(value));\n\nresponseFormat: { type: 'text' | 'json' } or\n{ type: 'json_schema', name, schema, strict? }.\nthinking: false to disable, or { budgetTokens?, level? } (minimal |\nlow | medium | high). Prefer one of budget or level \u2014 some providers reject both.\nOn Google/Gemini, false and budgetTokens: 0 map to thinkingLevel: MINIMAL\n(or LOW for models like Gemini 3.7 and Gemini 3 Pro where MINIMAL is unsupported)\nbecause Gemini 3 rejects thinkingBudget: 0. JSON responseFormat without an\nexplicit thinking setting or provider generationConfig.thinkingConfig default also\ndefaults Google thinking to MINIMAL (or LOW on Gemini 3.7 / Pro).\nOn Anthropic, level becomes output_config.effort with adaptive thinking (Opus and\nSonnet 4.6+), and false uses whatever the model accepts: disabled, between_tools\n(Sonnet 5.5), or effort low where thinking is always on (Opus 5.5, Fable). Budgets\nbecome adaptive thinking on models that removed them.\n\nProviders map what they support and ignore the rest. ProviderCapabilities.jsonResponse\nand ProviderCapabilities.thinking advertise support. Vendor-specific escapes such as\nVertex generationConfig remain available for anything not covered here.\nPromptItem: the portable prompt IR\nPromptItem is GenAIcode's provider-neutral intermediate representation:\nimport { image, prompt, system, toolResults, user } from 'genaicode';\n\nconst items = prompt(\n  system('Return a one-sentence caption.'),\n  user('Describe this image.', {\n    images: [image(base64Png, 'image/png')],\n  }),\n  toolResults({\n    callId: 'lookup-1',\n    name: 'lookup',\n    content: JSON.stringify({ locale: 'pl-PL' }),\n  }),\n);\n\nconst caption = await ai(items).text();\nApplications can make domain objects prompt-aware without coupling them to a provider:\nimport { asPrompt, system, user } from 'genaicode';\n\nconst supportTicket = (ticket: Ticket) =>\n  asPrompt(() => [system('Triage support tickets.'), user(JSON.stringify(ticket))]);\n\nawait ai(supportTicket(ticket)).text();\nTools\nconst calls = await ai('What is the weather in Warsaw?')\n  .tools([\n    {\n      name: 'weather',\n      description: 'Get current weather',\n      parameters: {\n        type: 'object',\n        properties: { city: { type: 'string' } },\n        required: ['city'],\n        additionalProperties: false,\n      },\n    },\n  ])\n  .toolCalls();\nGenAIcode normalizes tool calls but deliberately does not execute them. The application\nowns permissions, retries, and side effects.\nStreaming\nProviders that support native streaming expose a provider-neutral StreamEvent IR.\nRequest builders and chains offer .stream() and .streamText(). If a provider has no\nstream method, GenAIcode synthesizes a short stream from generate.\nfor await (const event of ai('Write a haiku about queues.').stream()) {\n  if (event.type === 'text-delta') process.stdout.write(event.text);\n  if (event.type === 'done') console.log('\\n', event.result.usage);\n}\nBuilt-in adapters declare capability metadata:\nai.provider.capabilities;\n// { streaming: true, tools: true, images: 'input', systemPrompt: true }\nMiddleware\nHook-style plugins remain the extension point. Built-in helpers cover common backend\nneeds without restoring a global registry:\nimport { cachePlugin, fallbackPlugin, genaicode, rateLimitPlugin, timingPlugin } from 'genaicode';\nimport { anthropic, openai } from 'genaicode/providers';\n\nconst ai = genaicode(openai({ model: 'your-model-name' }), {\n  plugins: [\n    timingPlugin(),\n    rateLimitPlugin({ concurrency: 2, minIntervalMs: 50 }),\n    cachePlugin({ maxEntries: 64 }),\n    fallbackPlugin({ providers: [anthropic({ model: 'your-claude-model' })] }),\n  ],\n});\nRetries\nRetries stay in application code. Use classifyError, isRetryable, and withRetry\nwhen you want shared classification without hidden policy:\nimport { withRetry } from 'genaicode';\n\nconst text = await withRetry(() => ai('Summarize the deploy notes.').text(), {\n  attempts: 3,\n  delayMs: 250,\n});\nSee retry guidance, semver policy, and the\nprovider package evaluation.\nProviders\nModels are provided in the adapter, through .model(...), or with environment variables:\n\n\n\nAdapter\nFactory\nEnvironment defaults\n\n\n\n\nOpenAI\nopenai()\nOPENAI_API_KEY, OPENAI_MODEL\n\n\nAnthropic\nanthropic()\nANTHROPIC_API_KEY, ANTHROPIC_MODEL\n\n\nGemini API / AI Studio\ngemini()\nGEMINI_API_KEY or API_KEY, GEMINI_MODEL\n\n\nVertex AI\nvertexAI()\nGOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, VERTEX_AI_MODEL\n\n\nOpenAI-compatible\nopenaiCompatible()\nExplicit options; OpenAI defaults also work\n\n\n\nimport { anthropic, gemini, openai, vertexAI } from 'genaicode/providers';\n\nconst viaOpenAI = genaicode(openai({ model: 'your-openai-model' }));\nconst viaClaude = genaicode(anthropic({ model: 'your-claude-model' }));\nconst viaGemini = genaicode(gemini({ model: 'your-gemini-model' }));\nconst viaVertex = genaicode(\n  vertexAI({\n    project: 'my-google-cloud-project',\n    location: 'global',\n    model: 'your-vertex-model',\n  }),\n);\nProvider-specific options stay at provider construction. For example, Anthropic exposes\nthinking and output-token defaults, while Gemini and Vertex expose SDK HTTP, auth, API\nversion, and generation configuration options.\nGitHub Models, Ollama, vLLM, and other compatible gateways use the OpenAI-compatible\nadapter:\nimport { openaiCompatible } from 'genaicode/providers';\n\nconst local = genaicode(\n  openaiCompatible({\n    name: 'local',\n    baseURL: 'http://localhost:11434/v1',\n    apiKey: 'local',\n    model: 'qwen3',\n  }),\n);\nImplementing a provider directly is intentionally small:\nimport type { ModelProvider } from 'genaicode';\n\nconst provider: ModelProvider = {\n  name: 'internal-gateway',\n  async generate(request) {\n    // Convert request.prompt to your API and normalize the response.\n    return { parts: [{ type: 'text', text: 'response' }] };\n  },\n};\nAll Anthropic, Google, and OpenAI conversion functions are public from\ngenaicode/providers, so gateways and tests can reuse them without instantiating clients.\nProvider E2E tests\nRun real-provider E2E tests locally with:\n\nThese tests are credential-gated and run only when provider-specific environment variables\nare set:\n\nOpenAI: OPENAI_API_KEY, OPENAI_MODEL\nAnthropic: ANTHROPIC_API_KEY, ANTHROPIC_MODEL\nGemini: GEMINI_API_KEY, GEMINI_MODEL\n\nBeyond the smoke call, E2E also covers portable responseFormat: { type: 'json' }\n(OpenAI, Gemini) and thinking (Anthropic disable; Gemini disable / level: 'minimal').\nCI/CD is configured in .github/workflows/provider-e2e.yaml. Each provider runs in its own\njob and only starts when both required secrets are configured in GitHub Actions.\nPlugins and hooks\nA provider plugin no longer needs registration in global configuration. It exports the\nsame small ModelProvider contract as a built-in adapter:\n// @acme/genaicode-bedrock\nimport type { ModelProvider } from 'genaicode';\n\nexport const bedrock = (options: BedrockOptions): ModelProvider => ({\n  name: 'bedrock',\n  capabilities: { streaming: false, tools: true, systemPrompt: true },\n  async generate(request) {\n    // Convert PromptItem[], call Bedrock, and return GenerationResult.\n    return { parts: [{ type: 'text', text: 'response' }] };\n  },\n});\nHook-style plugins use middleware. They can observe or rewrite requests and results,\nhandle errors, implement caches or fallbacks, and intentionally short-circuit a call.\nOptional stream middleware follows the same registration order.\nimport { definePlugin, genaicode } from 'genaicode';\nimport { openai } from 'genaicode/providers';\n\nconst timing = definePlugin({\n  name: 'timing',\n  async generate(request, next) {\n    const startedAt = performance.now();\n    try {\n      return await next(request);\n    } finally {\n      console.log('LLM request took', performance.now() - startedAt, 'ms');\n    }\n  },\n});\n\nconst ai = genaicode(openai({ model: 'your-model-name' }), {\n  plugins: [timing],\n});\nPlugins run in registration order, and each plugin may call next() once. This keeps the\nextension mechanism compatible with npm packages and ordinary imports without restoring\nthe 1.x runtime TypeScript loader or process-global plugin registry.\nFramework-shaped examples live under docs/examples/ (http-handler, queue-worker,\ncron-job).\nDesign boundaries\n\nBackend library, not a coding agent. genaicode/agents drives external agent CLIs; it\nadds no agent loop, prompts, or repository tools of its own.\nProvider-neutral core with no global configuration.\nExplicit models and credentials; environment variables are only provider defaults.\nImmutable request builders.\nConversation history is explicit; loops remain ordinary application code.\nNo hidden tool execution or hidden retries.\nThird-party providers and middleware use stable TypeScript contracts.\nProvider SDKs stay behind the genaicode/providers subpath; process spawning stays\nbehind genaicode/agents.\n\nMigration from 1.x\nGenAIcode 1.x was a coding agent. Version 2.0 deliberately replaces that product with a\nsmall backend LLM toolkit: a jQuery-like layer for portable prompts, provider adapters,\nconversation chains, and plugins.\nThe 1.x coding agent, browser UI, repository tools, and shell execution are not\ndeprecated compatibility features; they have been removed from 2.0. The 2.x\ngenaicode command only drives external agent CLIs (see From the command line). The GenAIcode name, PromptItem model, provider converters, and extensibility\ncontinue here in a smaller and more focused form.\nThe original coding agent remains available from the preserved\n1.x branch and the 1.x npm releases:\n\nIn 2.x, npx genaicode opens a chat with the coding-agent CLIs you have installed\n(Claude Code, Codex, opencode...) instead of the 1.x built-in agent.\nSee the pivot plan for the full scope, migration decisions, and the\nroadmap."
                ],
                "output": "readability",
                "pwd": "/data/archive/1792879566.16204",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-24T22:10:45.682570+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://github.com/gtanczyk/genaicode"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-10-24T22:10:45.675931+00:00",
                "index_texts": null,
                "output": "GitHub - gtanczyk/genaicode: One API and CLI over every coding agent (Claude Code, Codex, Copilot, Cursor, Gemini\u2026), plus a thin provider-neutral LLM layer for TypeScript.\u201d \u00b7 GitHub",
                "pwd": "/data/archive/1792879566.16204",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-24T22:10:45.513857+00:00",
                "status": "succeeded"
            }
        ],
        "wget": [
            {
                "cmd": [
                    "wget",
                    "--no-verbose",
                    "--adjust-extension",
                    "--convert-links",
                    "--force-directories",
                    "--span-hosts",
                    "--no-parent",
                    "-e",
                    "robots=off",
                    "--reject=js,mjs,woff,woff2,ttf,otf,eot,mp4,m4v,mov,webm,ogv,mkv,avi,flv",
                    "--timeout=120",
                    "--restrict-file-names=windows",
                    "--page-requisites",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) wget/GNU Wget 1.21.3",
                    "--compression=auto",
                    "--timestamping",
                    "https://github.com/gtanczyk/genaicode"
                ],
                "cmd_version": "GNU Wget 1.21.3",
                "end_ts": "2026-10-24T22:10:45.507053+00:00",
                "index_texts": null,
                "output": "github.com/gtanczyk/genaicode.html",
                "pwd": "/data/archive/1792879566.16204",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-24T22:10:34.888771+00:00",
                "status": "succeeded"
            }
        ]
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": null,
        "dom": null,
        "favicon": "favicon.ico",
        "git": null,
        "media": null,
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "GitHub - gtanczyk/genaicode: One API and CLI over every coding agent (Claude Code, Codex, Copilot, Cursor, Gemini\u2026), plus a thin provider-neutral LLM layer for TypeScript.\u201d \u00b7 GitHub",
        "warc": null,
        "wget": "github.com/gtanczyk/genaicode.html"
    },
    "link_dir": "/data/archive/1792879566.16204",
    "newest_archive_date": "2026-10-24T22:10:51.900684+00:00",
    "num_failures": 1,
    "num_outputs": 5,
    "oldest_archive_date": "2026-10-24T22:10:33.801781+00:00",
    "path": "/gtanczyk/genaicode",
    "schema": "Link",
    "scheme": "https",
    "snapshot_id": "1b9a56b1-1a66-4862-8405-28649a888357",
    "sources": [
        "/data/sources/1792879566-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1792879566.16204",
    "title": "GitHub - gtanczyk/genaicode: One API and CLI over every coding agent (Claude Code, Codex, Copilot, Cursor, Gemini\u2026), plus a thin provider-neutral LLM layer for TypeScript.\u201d \u00b7 GitHub",
    "updated": "2026-10-24T22:10:33.799905+00:00",
    "updated_date": "2026-10-24 22:10",
    "url": "https://github.com/gtanczyk/genaicode"
}