genaicode 2.17.2 → 2.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@
4
4
 
5
5
  ### Added
6
6
 
7
+ - Auto-approve in `genaicode chat` and `genaicode ui`: `a` on a permission request (or
8
+ `/auto`, or the Auto-approve chip in the browser) approves it and everything after it;
9
+ `/auto off` asks again. `-y` starts with it on.
10
+ - `genaicode embed` makes projects without a dev server work too: the agent writes a small
11
+ dev-only server that serves the pages with the panel injected, leaving the files and the
12
+ build untouched. It also keeps its shell commands few, since each one may need approval.
13
+
14
+ ## 2.18.0
15
+
16
+ ### Added
17
+
18
+ - `genaicode embed [request]`: your own agent adds genaicode to the project in the current
19
+ directory. It gets instructions (the Vite overlay; a development-only panel through
20
+ `genaicode/ui` for webpack, Next.js, Express and other Node dev servers; the in-app agent;
21
+ or `genaicode ui` alongside) and decides from what it reads in the project. The chat opens
22
+ in the terminal (`--ui`: browser), headless without one; `--print` prints the
23
+ instructions for any other agent. They ship as a Claude Code skill too,
24
+ `skills/genaicode-embed/SKILL.md`.
25
+ - One session in the terminal and the browser: `/web` in `genaicode chat` opens it in the
26
+ browser (`/web off` stops the server), `genaicode chat --web` does so at start, and Enter
27
+ in the terminal running `genaicode ui` continues there with the browser still connected.
28
+
7
29
  - `<AgentChat foldSteps>` (and `<AppAgentChat foldSteps>`): each turn's work (tools, edits,
8
30
  interim text) folds into one "N steps" line that opens on click, naming the running tool.
9
31
  The answer, errors, steering input and open approvals stay in view.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genaicode",
3
- "version": "2.17.2",
3
+ "version": "2.18.1",
4
4
  "description": "Drive the coding-agent CLIs you already have (Claude Code, Codex, Copilot, Cursor, Gemini, opencode...) behind one API and one CLI, plus a thin provider-neutral LLM layer.",
5
5
  "author": "Grzegorz Tańczyk",
6
6
  "repository": {
@@ -35,6 +35,7 @@
35
35
  "docs",
36
36
  "!docs/examples",
37
37
  "!docs/media",
38
+ "skills",
38
39
  "README.md",
39
40
  "LICENSE"
40
41
  ],
@@ -0,0 +1,381 @@
1
+ ---
2
+ name: genaicode-embed
3
+ description: Embed genaicode in a project so its developers chat with their own coding agent (Claude Code, Codex, Copilot...) from inside the running app. Use when asked to add genaicode, its Vite overlay, a dev agent panel, or an in-app agent drawer (genaicode/app-agent) to a project, with or without Vite.
4
+ ---
5
+
6
+ # Embed genaicode in this project
7
+
8
+ genaicode (npm package `genaicode`, Apache-2.0) drives the coding-agent CLIs a developer
9
+ already has installed (Claude Code, Codex, Copilot CLI, Cursor, Gemini, opencode...) behind
10
+ one API. Your job is to wire it into the project in the current directory so the
11
+ project's developers can talk to their agent from inside the running app.
12
+
13
+ There are four ways in, plus a last resort. Pick by what the project is, not by what is
14
+ more impressive. Always end with a working way in: "it doesn't fit" is not a result while
15
+ the project has HTML pages a browser can open.
16
+
17
+ 1. **Vite dev overlay** (`genaicode/vite`): for any app built with Vite. One plugin line;
18
+ in `vite dev` the page gets a wolf button that opens a chat with the agent working in
19
+ the project root, and the page's errors can be sent to the agent with one click.
20
+ Builds are untouched, nothing reaches production.
21
+ 2. **Dev panel without Vite** (`genaicode/ui`): the same chat for apps on webpack, Next.js,
22
+ Express, Fastify, Remix, Astro or any other Node dev server. The dev server process
23
+ starts the chat on 127.0.0.1 with `startEmbeddedWeb`, and a development-only component
24
+ on the page shows it in a frame. A few more lines than the plugin, same result.
25
+ 3. **In-app agent** (`genaicode/app-agent` on the server, `<AppAgentChat>` from
26
+ `genaicode/react` on the page): for a local app with its own Node server (an admin
27
+ console, an internal tool, an editor) whose users want the agent to act on the app's
28
+ data. The agent works the app through tools the app serves over MCP, every write waits
29
+ for the user's Approve, and an optional `dev` mode lets the agent edit the app's own
30
+ code and propose a commit.
31
+ 4. **A dev server of your own**: the project has pages but nothing serves them in
32
+ development (static HTML, files opened from `file://`, a build script that writes
33
+ `index.html`, a game jam entry with a size budget...). Write a small Node script that
34
+ serves those files on localhost and injects the panel into the HTML it serves, and an
35
+ npm script to start it. The files on disk and the build output stay untouched, so size
36
+ budgets and release builds are not affected.
37
+ 5. **Last resort**: no Node at all in the project and no HTML to serve (a Rails, Django or
38
+ Go app whose server you cannot change safely): tell the user to run `npx genaicode ui`
39
+ (browser) or `npx genaicode` (terminal) next to their dev server, and say why.
40
+
41
+ The agent CLI always runs on the machine that runs the server, with that machine's
42
+ login and files. Options 1, 2 and 4 exist only in development. The in-app agent is for apps
43
+ that run on the developer's own machine (or a trusted internal host behind real auth).
44
+ Never wire any of them into a public production site, and never expose them without auth.
45
+
46
+ Work with as few shell commands as you can: the user approves each one unless they turned
47
+ on auto-approve. Read files instead of listing them through the shell where your tools
48
+ allow, batch what must run into few commands, and do not run the dev server just to look.
49
+
50
+ ## Steps
51
+
52
+ 1. **Look before you change anything.** Read `package.json` (dependencies, scripts,
53
+ `workspaces`), the lockfile, the build and dev server config (`vite.config.*`,
54
+ `next.config.*`, `webpack.config.*`...), the server entry if there is one, and the root
55
+ component of the UI. Find out what actually serves the app in development. In a
56
+ monorepo, work in the package that owns the app; ask if that is unclear.
57
+ 2. **Choose the integration** (the user's request may already say which):
58
+ - Vite serves the app in development (React, Vue, Svelte, Solid, Preact, vanilla, also
59
+ Vite in front of a backend, Remix/React Router/Astro/SvelteKit on Vite): option 1.
60
+ - Another Node dev server: option 2.
61
+ - The user wants the agent to use the app itself (its data, its actions), or the app
62
+ is an internal tool with a Node server and a React UI: option 3, alone or next to
63
+ option 1 or 2.
64
+ - Pages but no dev server: option 4.
65
+ - Otherwise: option 5. Say why.
66
+ - If genaicode is already wired in, extend or fix what is there instead of adding a
67
+ second copy.
68
+ 3. **Install** `genaicode` as a dev dependency for options 1, 2 and 4, as a regular
69
+ dependency when the server imports `genaicode/app-agent`. Use the project's package
70
+ manager (lockfile: `package-lock.json` npm, `pnpm-lock.yaml` pnpm, `yarn.lock` yarn,
71
+ `bun.lockb`/`bun.lock` bun). If you may not run the install, add the dependency to
72
+ `package.json` with the version from your task and tell the user to run it. genaicode
73
+ needs Node 20+ and declares no React peer: the app brings React 18+ for
74
+ `genaicode/react`.
75
+ 4. **Wire it in** (recipes below), matching the project's style: TypeScript or
76
+ JavaScript, ESM or CJS, its formatter, its folder layout.
77
+ 5. **Check it**: run the project's type check, lint and tests if it has them, and a
78
+ production build. For options 1, 2 and 4 the production output must not contain
79
+ genaicode. Fix what you broke; do not "fix" unrelated failures. Remove build output
80
+ you created that the project does not track.
81
+ 6. **Report**: what you changed, file by file, and how to try it (the dev command, where
82
+ to click). Say that the agent uses its own CLI login and billing.
83
+
84
+ ## Recipe: Vite overlay
85
+
86
+ ```ts
87
+ // vite.config.ts
88
+ import { defineConfig } from 'vite';
89
+ import genaicode from 'genaicode/vite';
90
+
91
+ export default defineConfig({
92
+ plugins: [
93
+ // ...the project's plugins stay as they are
94
+ genaicode(),
95
+ ],
96
+ });
97
+ ```
98
+
99
+ - Add it to the existing `plugins` array; keep everything else. With a config function
100
+ (`defineConfig(({ mode }) => ...)`) it goes in the returned object the same way.
101
+ - A `vite.config.js` in a CommonJS package (no `"type": "module"`) still uses `import`:
102
+ Vite bundles the config. genaicode is ESM only (`require('genaicode/vite')` fails), so a
103
+ `vite.config.cjs` loads it with a dynamic import from an async config:
104
+ `module.exports = async () => ({ plugins: [react(), (await import('genaicode/vite')).default()] })`,
105
+ keeping the rest of the existing config in that object.
106
+ - Options, all optional: `agent` (e.g. `'claude'`, `'codex'`; default: first installed),
107
+ `model`, `effort`, `approveAll` (skip the agent's permission prompts; only if asked),
108
+ `port`, `captureErrors: false`. Leave the defaults unless the user asked.
109
+ - The chat runs on its own 127.0.0.1 port, only the app's own pages can frame it, and it
110
+ takes requests only from the same machine. No env vars or secrets are needed.
111
+
112
+ ## Recipe: dev panel without Vite
113
+
114
+ Server side, in the Node process that serves the app in development, and only there:
115
+
116
+ ```ts
117
+ import type { IncomingMessage, ServerResponse } from 'node:http';
118
+
119
+ // Development only: production never imports genaicode.
120
+ // The origins the app was opened at; the chat lets only these frame it, read per request.
121
+ const origins = new Set<string>();
122
+ let panel: Promise<{ url: string }> | undefined;
123
+ export function genaicodePanel() {
124
+ panel ??= import('genaicode/ui').then(({ startEmbeddedWeb }) =>
125
+ startEmbeddedWeb({ cwd: process.cwd(), frameAncestors: () => [...origins] }),
126
+ );
127
+ return panel;
128
+ }
129
+
130
+ // GET /__genaicode/session -> { url }. The url carries the chat's access token: hand it only
131
+ // to the app's own pages on this machine.
132
+ export async function genaicodeSession(req: IncomingMessage, res: ServerResponse) {
133
+ const address = req.socket.remoteAddress ?? '';
134
+ const local = address === '::1' || address.startsWith('127.') || address.startsWith('::ffff:127.');
135
+ const host = req.headers.host ?? '';
136
+ const loopbackHost = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(host);
137
+ if (!local || !loopbackHost || req.headers['sec-fetch-site'] !== 'same-origin') {
138
+ res.writeHead(403).end();
139
+ return;
140
+ }
141
+ origins.add(`http://${host}`); // localhost and 127.0.0.1 can both frame it, without a restart
142
+ const { url } = await genaicodePanel();
143
+ res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
144
+ res.end(JSON.stringify({ url }));
145
+ }
146
+ ```
147
+
148
+ - `frameAncestors` is read per request and must list every origin the app is opened at in
149
+ development (`http://localhost:3000`, `http://127.0.0.1:3000`...); the chat refuses other
150
+ frames. Collect them as above rather than capturing the first one. Behind a dev host name
151
+ (`app.localhost`, https), allow that name explicitly.
152
+ `startEmbeddedWeb` binds to 127.0.0.1 on a free port (`port` to fix one) and takes
153
+ `agent`, `model`, `effort`, `approveAll` like the Vite plugin.
154
+ - Start it once per process. Dev servers that reload server code (Next.js, nodemon...)
155
+ re-run modules: keep the promise on `globalThis` so a reload does not start a second chat.
156
+ - Where to hook the route:
157
+ - Express / Connect / node:http dev server: before the app's routes, behind
158
+ `process.env.NODE_ENV !== 'production'`.
159
+ - webpack-dev-server: `devServer.setupMiddlewares(middlewares, devServer)`, push a
160
+ middleware for `/__genaicode/session`.
161
+ - Next.js: a route handler such as `app/api/genaicode/route.ts` (folders starting with
162
+ `_` are private in the app router) that returns 404 unless
163
+ `process.env.NODE_ENV === 'development'`. It has no socket address, so check that the
164
+ `Host` header is `localhost` / `127.0.0.1` / `[::1]` with a port and that
165
+ `sec-fetch-site` is `same-origin`. Start the chat from the route on first use (Node
166
+ runtime: `export const runtime = 'nodejs'`), cached on `globalThis`.
167
+ - Fastify: a route under `if (dev)`; Koa: a middleware under `if (dev)`.
168
+
169
+ Page side, a component rendered only in development (`process.env.NODE_ENV === 'development'`,
170
+ `import.meta.env.DEV`...), loaded so the production bundle drops it (a dynamic `import()`
171
+ behind that check, or the framework's dev-only slot):
172
+
173
+ ```tsx
174
+ import { useEffect, useState } from 'react';
175
+
176
+ export function GenaicodePanel() {
177
+ const [url, setUrl] = useState<string>();
178
+ const [open, setOpen] = useState(false);
179
+ useEffect(() => {
180
+ fetch('/__genaicode/session')
181
+ .then((r) => (r.ok ? r.json() : undefined))
182
+ .then((s) => setUrl(s?.url));
183
+ }, []);
184
+ if (!url) return null;
185
+ return (
186
+ <>
187
+ <button onClick={() => setOpen(!open)} style={{ position: 'fixed', right: 16, bottom: 16, zIndex: 2147483647 }}>
188
+ Agent
189
+ </button>
190
+ {open && (
191
+ <iframe
192
+ src={url}
193
+ title="genaicode"
194
+ style={{
195
+ position: 'fixed',
196
+ right: 16,
197
+ bottom: 64,
198
+ width: 420,
199
+ height: '70vh',
200
+ zIndex: 2147483647,
201
+ border: 0,
202
+ borderRadius: 12,
203
+ boxShadow: '0 8px 32px #0004',
204
+ }}
205
+ />
206
+ )}
207
+ </>
208
+ );
209
+ }
210
+ ```
211
+
212
+ Not React? The same in the app's own framework or plain DOM: fetch the session, then a
213
+ fixed button that toggles a fixed iframe. Keep the token URL out of logs, `localStorage`
214
+ and anything the page sends elsewhere.
215
+
216
+ ## Recipe: a dev server of your own
217
+
218
+ For a project with pages but no dev server. One file, e.g. `tools/agent-dev.mjs` (match the
219
+ project's folder for scripts), plus `"agent": "node tools/agent-dev.mjs"` (or a name that
220
+ fits next to the existing scripts) in `package.json`:
221
+
222
+ ```js
223
+ // Development only: serves <dir> on 127.0.0.1 with the genaicode panel injected into HTML.
224
+ // The files on disk are not changed.
225
+ import { createServer } from 'node:http';
226
+ import { readFile, stat } from 'node:fs/promises';
227
+ import { extname, join, normalize, resolve, sep } from 'node:path';
228
+ import { startEmbeddedWeb } from 'genaicode/ui';
229
+
230
+ const root = resolve(process.argv[2] ?? 'build');
231
+ const port = Number(process.env.PORT ?? 5173);
232
+ const origins = new Set([`http://localhost:${port}`, `http://127.0.0.1:${port}`]);
233
+ const chat = await startEmbeddedWeb({ cwd: process.cwd(), frameAncestors: () => [...origins] });
234
+ const TYPES = {
235
+ '.html': 'text/html',
236
+ '.js': 'text/javascript',
237
+ '.mjs': 'text/javascript',
238
+ '.css': 'text/css',
239
+ '.json': 'application/json',
240
+ '.png': 'image/png',
241
+ '.svg': 'image/svg+xml',
242
+ '.wasm': 'application/wasm',
243
+ };
244
+ const PANEL = `<script type="module" src="/__genaicode/panel.js"></script>`;
245
+
246
+ createServer(async (req, res) => {
247
+ const url = new URL(req.url ?? '/', 'http://x');
248
+ if (url.pathname === '/__genaicode/session') {
249
+ // The token: only this machine (a loopback Host, which stops DNS rebinding), only the
250
+ // app's own pages.
251
+ if (!origins.has(`http://${req.headers.host}`) || req.headers['sec-fetch-site'] !== 'same-origin') {
252
+ return res.writeHead(403).end();
253
+ }
254
+ res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
255
+ return res.end(JSON.stringify({ url: chat.url }));
256
+ }
257
+ if (url.pathname === '/__genaicode/panel.js') {
258
+ res.writeHead(200, { 'content-type': 'text/javascript' });
259
+ return res.end(PANEL_SCRIPT);
260
+ }
261
+ let file;
262
+ try {
263
+ file = normalize(join(root, decodeURIComponent(url.pathname)));
264
+ } catch {
265
+ return res.writeHead(400).end();
266
+ }
267
+ if (file !== root && !file.startsWith(root + sep)) return res.writeHead(403).end();
268
+ if ((await stat(file).catch(() => null))?.isDirectory()) file = join(file, 'index.html');
269
+ const body = await readFile(file).catch(() => null);
270
+ if (!body) return res.writeHead(404).end('not found');
271
+ const type = TYPES[extname(file)] ?? 'application/octet-stream';
272
+ res.writeHead(200, { 'content-type': type, 'cache-control': 'no-store' });
273
+ res.end(type === 'text/html' ? injectPanel(body.toString()) : body);
274
+ }).listen(port, '127.0.0.1', () =>
275
+ console.log(`http://localhost:${port} (genaicode panel: the button in the corner)`),
276
+ );
277
+
278
+ function injectPanel(html) {
279
+ return html.includes('</body>') ? html.replace('</body>', `${PANEL}</body>`) : html + PANEL;
280
+ }
281
+ ```
282
+
283
+ `PANEL_SCRIPT` is the page side as plain DOM, the same as the React component in the
284
+ recipe above: fetch `/__genaicode/session`, then a fixed button in a corner that toggles a
285
+ fixed iframe with `src` set to the session `url`. Keep it out of the app's own code.
286
+
287
+ - Listen on `127.0.0.1` only, and hand out the token only for a loopback `Host` and
288
+ `Sec-Fetch-Site: same-origin`. Together they keep the token on this machine and away from
289
+ other sites, including DNS-rebinding pages.
290
+ - Serve what the developer actually opens: the build output folder if the project builds
291
+ one (start or chain its build or watch script when it has one, e.g. with `--watch` or a
292
+ second process), otherwise the source folder with the HTML.
293
+ - Optional, for the agent to see what breaks: in `PANEL_SCRIPT`, collect `error` and
294
+ `unhandledrejection` events and offer a "Fix N errors" button that POSTs them to a
295
+ `/__genaicode/fix` route (same checks as the session route) which calls
296
+ `chat.controller.submit('Fix these errors in the page …' + details)`.
297
+ - A page that reloads itself on change (a watch build) is the developer's own; do not
298
+ add a live-reload system for this.
299
+
300
+ ## Recipe: in-app agent
301
+
302
+ Server side, wherever the Node server is created:
303
+
304
+ ```ts
305
+ import { createAppAgent, createAppAgentHandler } from 'genaicode/app-agent';
306
+
307
+ const agent = createAppAgent({
308
+ name: 'the shop admin', // how the agent calls the app
309
+ mcpUrl: () => `http://127.0.0.1:${port}/api/agent/mcp`, // this server, reachable from this machine
310
+ tools: [
311
+ { name: 'orders', description: 'List open orders.', run: () => JSON.stringify(listOrders()) },
312
+ {
313
+ name: 'refund',
314
+ description: 'Refund an order.',
315
+ write: true, // shows Approve / Reject; runs only after Approve
316
+ inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
317
+ run: ({ id }) => refund(String(id)),
318
+ },
319
+ ],
320
+ pages: () => ['/', '/orders'], // optional: page context and "take me there" chips
321
+ dev: { cwd: process.cwd(), commands: ['npm test'] }, // optional: agent may edit the app's code
322
+ });
323
+ const handleAgent = createAppAgentHandler(agent, {
324
+ // Required as soon as the server listens beyond loopback: reuse the app's own auth check.
325
+ // authorize: (req) => isAdmin(req),
326
+ });
327
+ ```
328
+
329
+ Mount `handleAgent(req, res)` before the app's own routes; it returns `true` when it
330
+ answered (everything under `/api/agent`, or `basePath`):
331
+
332
+ - node:http: `if (await handleAgent(req, res)) return;` at the top of the request handler.
333
+ - Express / Connect: `app.use((req, res, next) => handleAgent(req, res).then((done) => done || next(), next));`
334
+ (a JSON body parser before it is fine: it reads `req.body` when one is there).
335
+ - Fastify: an `onRequest` hook that calls `reply.hijack()` and `handleAgent(request.raw, reply.raw)`
336
+ for URLs under the base path.
337
+ - Vite dev server with no backend: a small plugin whose `configureServer(server)` adds the
338
+ same middleware to `server.middlewares`; build `mcpUrl` from the server's real port
339
+ (`server.httpServer.address()`), not the configured one.
340
+ - Next.js pages router: `pages/api/agent/[...path].ts` with
341
+ `export const config = { api: { bodyParser: false } }` calling `handleAgent(req, res)`.
342
+ The app router has no Node `req`/`res`: use a custom server or the pages route.
343
+ - Call `handleAgent.close()` when the server shuts down.
344
+
345
+ Tools are the point of the in-app agent: expose a few read tools over the app's existing
346
+ data or services and, only where it makes sense, write tools with `write: true`. Reuse the
347
+ app's own functions; never hand the agent raw database or shell access through a tool. If
348
+ the app has nothing worth exposing yet, start with `pages` and one read tool, and tell the
349
+ user where to add more.
350
+
351
+ Page side, in the React tree (React 18+):
352
+
353
+ ```tsx
354
+ import { AppAgentChat } from 'genaicode/react';
355
+ import 'genaicode/react/styles.css';
356
+
357
+ // Inside a fixed-height container: a drawer, a side panel, a route of its own.
358
+ <div style={{ position: 'fixed', right: 0, top: 0, bottom: 0, width: 420 }}>
359
+ <AppAgentChat currentPath={location.pathname} onNavigate={(path) => navigate(path)} />
360
+ </div>;
361
+ ```
362
+
363
+ - Use the app's router for `currentPath` and `onNavigate` (React Router `useLocation` /
364
+ `useNavigate`, Next `useRouter`...). Without a router, `location.pathname` and
365
+ `location.assign`.
366
+ - Put the drawer behind a toggle button that matches the app's UI, closed by default.
367
+ - Theme with `--gc-*` CSS custom properties (`--gc-bg`, `--gc-text`, `--gc-accent`,
368
+ `--gc-font`...) or `theme="light" | "dark"`, to match the app.
369
+ - Not React? Use the HTTP routes directly: `GET /api/agent/events` (server-sent
370
+ `AppAgentState`), `POST /api/agent/send {text}`, `/stop`, `/reset`, `/actions/:id`.
371
+
372
+ ## Rules
373
+
374
+ - Keep changes small and in the project's own style. Do not restructure the app, upgrade
375
+ unrelated dependencies, or reformat files you did not need to touch.
376
+ - Do not commit, push, or create branches unless the user asks.
377
+ - Do not add API keys, tokens or env vars: the agent CLI brings its own login.
378
+ - Do not turn on `approveAll` or the agents' "yolo" modes unless the user asks.
379
+ - These recipes are starting points, not templates to paste: adapt them to what the
380
+ project already does. If something does not fit, say what and why instead of forcing it.
381
+ - Docs: https://github.com/gtanczyk/genaicode#readme