codehere 0.3.3 → 0.4.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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Tests](https://img.shields.io/badge/tests-362%2F362-brightgreen)]()
8
8
  [![Vulnerabilities](https://img.shields.io/badge/vulnerabilities-0-brightgreen)]()
9
9
 
10
- codehere is **a PM for your AI coding agents.** Delegate a task. codehere picks the right specialist (Claude Code, Codex, Aider, or OpenCode), spawns it, streams its work back to you, and hands you a plain-English Final Report with a **trust score on every file changed**. Today, Claude Code gets full pre-execution safety-hook enforcement on every tool call; other adapters are session-traced and reported, with per-tool hook parity planned for v0.4.
10
+ codehere is **a PM for your AI coding agents.** Delegate a task. codehere picks the right specialist (Claude Code, Codex, Aider, OpenCode, or Gemini), spawns it, streams its work back to you, and hands you a plain-English Final Report with a **trust score on every file changed**. Today, Claude Code gets full pre-execution safety-hook enforcement on every tool call; other adapters are session-traced and reported, with per-tool hook parity planned for v0.4.
11
11
 
12
12
  One manager, many specialists, every action recorded.
13
13
 
@@ -25,7 +25,7 @@ One manager, many specialists, every action recorded.
25
25
  | 1 | **Install** | `npm install -g codehere` (Node 20+) |
26
26
  | 2 | **Detect environment** | `codehere setup` — checks API key, finds your repo |
27
27
  | 3 | **Install the safety hook** | `codehere hook install` — every Claude Code tool call now audited |
28
- | 4 | **Install at least one specialist** | Claude Code: `npm i -g @anthropic-ai/claude-code` (Codex / Aider / OpenCode also supported) |
28
+ | 4 | **Install at least one specialist** | Claude Code: `npm i -g @anthropic-ai/claude-code` (Codex / Aider / OpenCode / Gemini also supported · see [docs/integrations/](../docs/integrations/) for Cursor MCP setup) |
29
29
  | 5 | **Scaffold this repo** | `cd /your/repo && codehere init` — creates `.codehere/CONTEXT.md`, `HANDOVER.md`, `PROMPTS.md` |
30
30
  | 6 | **Fill in CONTEXT.md** | 5 minutes. The agent will follow what's written there. |
31
31
 
@@ -57,7 +57,7 @@ Context + End State + Constraints
57
57
 
58
58
  1. Spawns the agent with the right headless flags
59
59
  2. Streams the steps back as readable progress · *"Read src/auth.ts · Edit src/auth.ts · Run npm test"*
60
- 3. For Claude Code tasks: routes every tool call through the codehere safety hook — you see *"🛡 5 safe · 0 blocked"* grow live. For Codex / Aider / OpenCode tasks: emits the agent's native event stream (no per-tool blocking yet)
60
+ 3. For Claude Code tasks: routes every tool call through the codehere safety hook — you see *"🛡 5 safe · 0 blocked"* grow live. For Codex / Aider / OpenCode / Gemini tasks: emits the agent's native event stream (no per-tool blocking yet)
61
61
  4. On completion, hands you a **Final report**: files changed with `+/-` lines, trust score per file, test pass/fail, token usage
62
62
 
63
63
  Claude Code tool calls are logged to `~/.codehere/audit/hook-events.jsonl`; spawned-task lifecycle (any adapter) is logged to `~/.codehere/sessions.jsonl`. Open-source MIT. Node 20+.
@@ -138,7 +138,7 @@ For depth beyond this README:
138
138
 
139
139
  - **Node.js 20+** (pinned in `.nvmrc` and `engines.node`)
140
140
  - **macOS or Linux** · Windows via WSL works for development; native Windows is on the v0.4 roadmap
141
- - **Optional but recommended:** at least one supported agent (Claude Code, Codex, Aider, or OpenCode) installed before running `codehere start`
141
+ - **Optional but recommended:** at least one supported agent (Claude Code, Codex, Aider, OpenCode, or Gemini) installed before running `codehere start`
142
142
 
143
143
  ## License
144
144
 
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Gemini adapter — F2 fifth concrete AgentAdapter (v0.4 sprint Track 1a).
3
+ *
4
+ * Wraps the official Google `gemini` CLI (`@google/gemini-cli`) as a subprocess.
5
+ * Headless mode: `gemini -p "<prompt>" -o stream-json --approval-mode=yolo`.
6
+ *
7
+ * Event schema verified against the installed gemini-cli source
8
+ * (StreamJsonFormatter / JsonStreamEventType in @google/gemini-cli bundle):
9
+ * init — { session_id, model, timestamp }
10
+ * message — { role: "user"|"assistant", content, delta?: bool }
11
+ * tool_use — { tool_name, tool_id, parameters }
12
+ * tool_result — tool execution output (best-effort parse — schema sparse)
13
+ * error — { error: {...} }
14
+ * result — { status: "success"|"error", stats?, error?: { type, message } }
15
+ *
16
+ * Token usage from result.stats: { total_tokens, input_tokens, output_tokens, cached }.
17
+ *
18
+ * Sandbox: Gemini's --approval-mode=yolo enables sandboxing by default. codehere is
19
+ * the external safety layer — its PreToolUse hook (F0) is where destructive-command
20
+ * scanning happens. We DISABLE Gemini's sandbox via GEMINI_SANDBOX=false so file
21
+ * writes / shell commands aren't blocked at the wrong layer. (Note: today's hook
22
+ * still only wraps Claude Code; for Gemini, codehere observes via session events
23
+ * and audits at task-completion. Per-tool blocking parity for non-Claude adapters
24
+ * is v0.4+ work — see project_v04_sprint.md Track 3.)
25
+ *
26
+ * Same structural pattern as ClaudeCodeAdapter / CodexAdapter / AiderAdapter:
27
+ * event buffer, late-joiner replay, SIGTERM→SIGKILL escalation, known-error
28
+ * fingerprints. Finalizes on `close` (not `exit`) so trailing stdout flushes.
29
+ * Kills the process group (negative pid) so subagents under gemini exit too.
30
+ */
31
+ import { spawn, execFileSync } from 'child_process';
32
+ import { EventEmitter } from 'events';
33
+ import { randomUUID } from 'crypto';
34
+ import { ProgressAggregator } from './progress-aggregator.js';
35
+ import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
36
+ // `--approval-mode=yolo` auto-accepts every tool action without prompting (the
37
+ // older `--yolo` flag is deprecated). codehere's safety hook is the external
38
+ // gate, so gemini-side prompts would block headless tasks.
39
+ //
40
+ // `-o stream-json` — JSONL events, one per line, schema verified above.
41
+ //
42
+ // `--skip-trust` — skip the per-workspace trust prompt that gemini shows on
43
+ // first run in a new directory.
44
+ const HEADLESS_FLAGS = [
45
+ '--approval-mode',
46
+ 'yolo',
47
+ '--skip-trust',
48
+ '-o',
49
+ 'stream-json',
50
+ ];
51
+ // Error patterns scoped to STDERR / result.error / error events only — NOT
52
+ // every JSON-serialized event (which would false-positive on tool output).
53
+ const ERROR_PATTERNS = [
54
+ {
55
+ code: 'auth_missing',
56
+ regex: /please set an auth method|GEMINI_API_KEY|GOOGLE_API_KEY|GOOGLE_GENAI_USE_VERTEXAI|GOOGLE_GENAI_USE_GCA|GOOGLE_APPLICATION_CREDENTIALS/i,
57
+ message: 'Gemini auth missing.',
58
+ recovery: 'Set GEMINI_API_KEY env var (free tier at https://aistudio.google.com/apikey) — or configure Vertex AI / Application Default Credentials per https://google-gemini.github.io/gemini-cli/docs/get-started/authentication.html',
59
+ },
60
+ {
61
+ code: 'auth_invalid',
62
+ regex: /authentication\s+failed|401\s*unauth|invalid\s+api\s+key|api\s+key\s+not\s+valid/i,
63
+ message: 'Gemini API key is rejected.',
64
+ recovery: 'Verify GEMINI_API_KEY at https://aistudio.google.com/apikey and re-export.',
65
+ },
66
+ {
67
+ code: 'rate_limit',
68
+ regex: /rate.?limit(ed)?|HTTP\s*429|too many requests/i,
69
+ message: 'Gemini rate-limited the request.',
70
+ recovery: 'Wait a minute and retry, or check your Google AI Studio quota.',
71
+ },
72
+ {
73
+ code: 'quota_exceeded',
74
+ regex: /\bRESOURCE_EXHAUSTED\b|quota[\s_]exceeded|insufficient[_\s]quota/i,
75
+ message: 'Google AI Studio / Vertex quota exhausted.',
76
+ recovery: 'Check quota at https://aistudio.google.com/ or top up your Google Cloud billing account.',
77
+ },
78
+ ];
79
+ function scanForKnownError(text) {
80
+ for (const p of ERROR_PATTERNS) {
81
+ if (p.regex.test(text))
82
+ return { code: p.code, message: p.message, recovery: p.recovery };
83
+ }
84
+ return null;
85
+ }
86
+ export function spawnGemini(prompt, options = {}) {
87
+ const sessionId = randomUUID();
88
+ const startedAt = Date.now();
89
+ const emitter = new EventEmitter();
90
+ const buffer = [];
91
+ let statusValue = 'running';
92
+ let killRequested = false;
93
+ const cwd = options.cwd ?? process.cwd();
94
+ const snapshot = snapshotCwd(cwd);
95
+ const aggregator = new ProgressAggregator('gemini');
96
+ let stepsSnapshot = [];
97
+ let artifactsSnapshot;
98
+ let textAccum = '';
99
+ let usageSnapshot;
100
+ let errorEmitted = false;
101
+ const emit = (event) => {
102
+ buffer.push(event);
103
+ emitter.emit('event', event);
104
+ if (aggregator.ingest(event)) {
105
+ stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
106
+ const snapEvt = {
107
+ type: 'codehere:steps',
108
+ timestamp: Date.now(),
109
+ data: { steps: stepsSnapshot },
110
+ };
111
+ buffer.push(snapEvt);
112
+ emitter.emit('event', snapEvt);
113
+ }
114
+ // Accumulate assistant text for the test-summary parser. Real schema:
115
+ // { type: 'message', role: 'assistant', content: '...', delta?: bool }.
116
+ // Both deltas (streaming chunks) and full messages contribute.
117
+ if (event.type === 'message') {
118
+ const data = event.data;
119
+ if (data?.role === 'assistant' && typeof data.content === 'string') {
120
+ textAccum += data.content;
121
+ // Only add a newline at non-delta boundaries to avoid breaking up streamed words
122
+ if (!event.data?.delta)
123
+ textAccum += '\n';
124
+ }
125
+ }
126
+ // tool_result event — schema sparse in source; accept common output fields.
127
+ if (event.type === 'tool_result') {
128
+ const data = event.data;
129
+ const t = data?.output ?? data?.result ?? data?.content;
130
+ if (typeof t === 'string')
131
+ textAccum += t + '\n';
132
+ }
133
+ // Token usage comes ONLY in the final `result` event with status:'success',
134
+ // shape { stats: { input_tokens, output_tokens, total_tokens, cached } }.
135
+ if (event.type === 'result') {
136
+ const data = event.data;
137
+ if (data?.status === 'success' && data.stats) {
138
+ const s = data.stats;
139
+ usageSnapshot = {
140
+ inputTokens: Number(s.input_tokens ?? 0),
141
+ outputTokens: Number(s.output_tokens ?? 0),
142
+ ...(s.cached ? { cacheReadTokens: Number(s.cached) } : {}),
143
+ };
144
+ }
145
+ }
146
+ // Scoped error matching: ONLY look at stderr lines and the FAILURE form of
147
+ // the `result` event (status:"error"). DO NOT scan `error` stream events
148
+ // because gemini's headless docs explicitly classify them as "Non-fatal
149
+ // warnings and system errors" — e.g. loop-detection warnings emit `error`
150
+ // events with severity:"warning". The terminal failure signal is the final
151
+ // `result` event with status:"error". Treating non-fatal `error` events as
152
+ // a terminal codehere:error would falsely mark successful sessions as failed.
153
+ if (!errorEmitted) {
154
+ let text = '';
155
+ const data = event.data;
156
+ if (event.type === 'stderr' && data && typeof data.line === 'string') {
157
+ text = data.line;
158
+ }
159
+ else if (event.type === 'result') {
160
+ const r = data;
161
+ if (r?.status === 'error' && r.error) {
162
+ text = `${r.error.type ?? ''} ${r.error.message ?? ''}`;
163
+ }
164
+ }
165
+ if (text) {
166
+ const hit = scanForKnownError(text);
167
+ if (hit) {
168
+ errorEmitted = true;
169
+ const errEvent = {
170
+ type: 'codehere:error',
171
+ timestamp: Date.now(),
172
+ data: { code: hit.code, message: hit.message, recovery: hit.recovery },
173
+ };
174
+ buffer.push(errEvent);
175
+ emitter.emit('event', errEvent);
176
+ }
177
+ }
178
+ }
179
+ };
180
+ // Disable Gemini's built-in sandbox — codehere is the external safety layer.
181
+ // GEMINI_SANDBOX=false trumps any sandbox-on default that --approval-mode=yolo
182
+ // would otherwise enable. (Settings precedence: env > settings.json > default.)
183
+ const spawnEnv = {
184
+ ...process.env,
185
+ GEMINI_SANDBOX: 'false',
186
+ ...(options.env ?? {}),
187
+ };
188
+ let child;
189
+ try {
190
+ child = spawn('gemini', ['-p', prompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
191
+ cwd,
192
+ env: spawnEnv,
193
+ stdio: ['ignore', 'pipe', 'pipe'],
194
+ // Detach into its own process group so kill() can reap subagents.
195
+ detached: true,
196
+ });
197
+ // Don't keep the parent alive on this child alone.
198
+ child.unref();
199
+ }
200
+ catch (err) {
201
+ statusValue = 'error';
202
+ const msg = err instanceof Error ? err.message : String(err);
203
+ queueMicrotask(() => emit({ type: 'error', timestamp: Date.now(), data: { message: msg } }));
204
+ return {
205
+ sessionId,
206
+ startedAt,
207
+ prompt,
208
+ agent: 'gemini',
209
+ get status() {
210
+ return statusValue;
211
+ },
212
+ get steps() {
213
+ return stepsSnapshot;
214
+ },
215
+ get artifacts() {
216
+ return artifactsSnapshot;
217
+ },
218
+ subscribe(handler) {
219
+ for (const e of buffer)
220
+ handler(e);
221
+ emitter.on('event', handler);
222
+ return () => emitter.off('event', handler);
223
+ },
224
+ kill() {
225
+ /* already not running */
226
+ },
227
+ };
228
+ }
229
+ let stdoutBuf = '';
230
+ child.stdout?.on('data', (chunk) => {
231
+ stdoutBuf += chunk.toString('utf-8');
232
+ let idx;
233
+ while ((idx = stdoutBuf.indexOf('\n')) >= 0) {
234
+ const line = stdoutBuf.slice(0, idx).trim();
235
+ stdoutBuf = stdoutBuf.slice(idx + 1);
236
+ if (!line)
237
+ continue;
238
+ try {
239
+ const data = JSON.parse(line);
240
+ emit({ type: data.type ?? 'gemini_event', timestamp: Date.now(), data });
241
+ }
242
+ catch {
243
+ emit({ type: 'stdout', timestamp: Date.now(), data: { line } });
244
+ }
245
+ }
246
+ });
247
+ child.stderr?.on('data', (chunk) => {
248
+ const text = chunk.toString('utf-8');
249
+ for (const line of text.split('\n')) {
250
+ if (line.trim())
251
+ emit({ type: 'stderr', timestamp: Date.now(), data: { line } });
252
+ }
253
+ });
254
+ // Use `close` (after all stdio has drained), not `exit` (which can fire while
255
+ // unread stdout is still in-flight). Flush any final un-newlined buffered line.
256
+ child.on('close', (code, signal) => {
257
+ if (stdoutBuf.trim()) {
258
+ const line = stdoutBuf.trim();
259
+ stdoutBuf = '';
260
+ try {
261
+ const data = JSON.parse(line);
262
+ emit({ type: data.type ?? 'gemini_event', timestamp: Date.now(), data });
263
+ }
264
+ catch {
265
+ emit({ type: 'stdout', timestamp: Date.now(), data: { line } });
266
+ }
267
+ }
268
+ statusValue =
269
+ killRequested || signal !== null
270
+ ? 'killed'
271
+ : errorEmitted
272
+ ? 'error'
273
+ : code === 0
274
+ ? 'completed'
275
+ : 'error';
276
+ if (aggregator.finalize(statusValue === 'completed' ? 'completed' : statusValue === 'killed' ? 'killed' : 'error')) {
277
+ stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
278
+ emit({ type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } });
279
+ }
280
+ if (statusValue === 'completed') {
281
+ try {
282
+ artifactsSnapshot = { ...extractArtifacts(snapshot, textAccum), usage: usageSnapshot };
283
+ emit({ type: 'codehere:artifacts', timestamp: Date.now(), data: artifactsSnapshot });
284
+ }
285
+ catch {
286
+ /* best-effort */
287
+ }
288
+ }
289
+ emit({
290
+ type: 'exit',
291
+ timestamp: Date.now(),
292
+ data: { code, signal, status: statusValue, durationMs: Date.now() - startedAt },
293
+ });
294
+ });
295
+ child.on('error', (err) => {
296
+ statusValue = 'error';
297
+ emit({ type: 'error', timestamp: Date.now(), data: { message: err.message } });
298
+ });
299
+ return {
300
+ sessionId,
301
+ startedAt,
302
+ prompt,
303
+ agent: 'gemini',
304
+ get status() {
305
+ return statusValue;
306
+ },
307
+ get steps() {
308
+ return stepsSnapshot;
309
+ },
310
+ get artifacts() {
311
+ return artifactsSnapshot;
312
+ },
313
+ subscribe(handler) {
314
+ for (const e of buffer)
315
+ handler(e);
316
+ emitter.on('event', handler);
317
+ return () => emitter.off('event', handler);
318
+ },
319
+ kill() {
320
+ if (statusValue !== 'running')
321
+ return;
322
+ killRequested = true;
323
+ // Process-group kill via negative pid is POSIX-only — Node on Windows
324
+ // throws ESRCH for that. Fall back to direct child.kill on Windows.
325
+ const isPosix = process.platform !== 'win32';
326
+ const sendSignal = (sig) => {
327
+ if (isPosix && child.pid) {
328
+ try {
329
+ process.kill(-child.pid, sig);
330
+ return;
331
+ }
332
+ catch {
333
+ /* fall through to child.kill */
334
+ }
335
+ }
336
+ try {
337
+ child.kill(sig);
338
+ }
339
+ catch {
340
+ /* already dead */
341
+ }
342
+ };
343
+ sendSignal('SIGTERM');
344
+ setTimeout(() => {
345
+ if (statusValue === 'running')
346
+ sendSignal('SIGKILL');
347
+ }, 3000);
348
+ },
349
+ };
350
+ }
351
+ export const GeminiAdapter = {
352
+ id: 'gemini',
353
+ label: 'Gemini',
354
+ // Match `gemini`, `node /path/.../gemini`, and `./gemini` invocations.
355
+ processPattern: /(?:^|\/)gemini(?:\s|$)|node\s+\S*gemini/,
356
+ isInstalled() {
357
+ try {
358
+ const out = execFileSync('which', ['gemini'], { encoding: 'utf-8', timeout: 2000 });
359
+ return out.trim().length > 0;
360
+ }
361
+ catch {
362
+ return false;
363
+ }
364
+ },
365
+ spawn(prompt, options) {
366
+ return spawnGemini(prompt, options);
367
+ },
368
+ checkReadiness() {
369
+ const installed = this.isInstalled();
370
+ if (!installed) {
371
+ return {
372
+ id: this.id,
373
+ label: this.label,
374
+ state: 'needs_install',
375
+ installed: false,
376
+ installHint: {
377
+ command: 'npm install -g @google/gemini-cli',
378
+ url: 'https://github.com/google-gemini/gemini-cli',
379
+ },
380
+ };
381
+ }
382
+ // Gemini supports GEMINI_API_KEY (free tier at aistudio.google.com), GOOGLE_API_KEY,
383
+ // Vertex AI (GOOGLE_GENAI_USE_VERTEXAI=true), GCA (GOOGLE_GENAI_USE_GCA=true), or
384
+ // cached Google login. Can't verify without a roundtrip — let runtime catch
385
+ // real failures via ERROR_PATTERNS.auth_missing on the result.error event.
386
+ return { id: this.id, label: this.label, state: 'ready', installed: true };
387
+ },
388
+ };
389
+ //# sourceMappingURL=gemini-adapter.js.map
@@ -99,6 +99,8 @@ export class ProgressAggregator {
99
99
  return this.ingestClaude(type, data);
100
100
  if (this.agentId === 'codex')
101
101
  return this.ingestCodex(type, data);
102
+ if (this.agentId === 'gemini')
103
+ return this.ingestGemini(type, data);
102
104
  return false;
103
105
  }
104
106
  /** Flush: mark every still-active step as failed (called on abnormal exit). */
@@ -157,6 +159,42 @@ export class ProgressAggregator {
157
159
  }
158
160
  return false;
159
161
  }
162
+ /**
163
+ * Gemini's stream-json schema (verified against @google/gemini-cli source):
164
+ * tool_use: { tool_name, tool_id, parameters } — adds an active step
165
+ * tool_result: { tool_id, ...output } — closes by tool_id
166
+ *
167
+ * We reuse the Claude-shaped tool labelling as a best-effort default — Gemini's
168
+ * tool surface (Read, Edit, Write, Shell, Grep, Glob, etc.) overlaps heavily
169
+ * with the Claude SDK tool naming since both emerged from the same agent
170
+ * lineage. If a specific Gemini tool name doesn't match, the default branch in
171
+ * `labelForClaudeToolUse` falls back to the raw tool name.
172
+ */
173
+ ingestGemini(type, data) {
174
+ if (type === 'tool_use') {
175
+ const id = String(data.tool_id ?? '');
176
+ const name = String(data.tool_name ?? 'tool');
177
+ const label = labelForClaudeToolUse(name, data.parameters);
178
+ const idx = this.addStep(label, 'active');
179
+ if (id)
180
+ this.toolUseIndex.set(id, idx);
181
+ return true;
182
+ }
183
+ if (type === 'tool_result') {
184
+ const id = String(data.tool_id ?? '');
185
+ const idx = id ? this.toolUseIndex.get(id) : undefined;
186
+ if (idx !== undefined) {
187
+ const step = this._steps[idx];
188
+ // Gemini's tool_result may carry an `error` field on failure; treat any
189
+ // non-undefined `error` as failed. Otherwise mark done.
190
+ const isError = data.error !== undefined && data.error !== null;
191
+ step.state = isError ? 'failed' : 'done';
192
+ step.completedAt = Date.now();
193
+ return true;
194
+ }
195
+ }
196
+ return false;
197
+ }
160
198
  ingestCodex(type, data) {
161
199
  if (type !== 'item.completed')
162
200
  return false;
@@ -17,6 +17,7 @@ import { ClaudeCodeAdapter } from './claude-code-adapter.js';
17
17
  import { CodexAdapter } from './codex-adapter.js';
18
18
  import { AiderAdapter } from './aider-adapter.js';
19
19
  import { OpenCodeAdapter } from './opencode-adapter.js';
20
+ import { GeminiAdapter } from './gemini-adapter.js';
20
21
  import { buildGenericAdapter, loadAgentsConfig } from './generic-cli-adapter.js';
21
22
  import { buildConductorAdapter } from './conductor-adapter.js';
22
23
  import { seedAgentsConfig } from './agents-config-seed.js';
@@ -27,7 +28,7 @@ import { appendSpawned, appendCompleted, loadRecent } from './session-persistenc
27
28
  // order. Built-in adapters first; user-config-driven ones appended from
28
29
  // ~/.codehere/agents.yaml (generic-cli-adapter). isInstalled() gates visibility
29
30
  // so an adapter registering for an un-installed CLI is harmless.
30
- const BUILTIN_REGISTRY = [ClaudeCodeAdapter, CodexAdapter, AiderAdapter, OpenCodeAdapter];
31
+ const BUILTIN_REGISTRY = [ClaudeCodeAdapter, CodexAdapter, AiderAdapter, OpenCodeAdapter, GeminiAdapter];
31
32
  // Seed the default agents.yaml if this user has never configured one, so Gemini
32
33
  // / Cursor / Pi / Antigravity / Goose appear in the registry pre-wired. They
33
34
  // stay hidden in the UI until isInstalled() flips to true. Idempotent.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehere",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "A PM for your AI coding agents. Delegate, orchestrate, and audit Claude Code, Codex, Aider, and OpenCode from one local web UI — every AI action traced, every file scored.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",