imhcode 2.0.5 → 3.0.0

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.
@@ -129,9 +129,14 @@ export class ClaudeCodeCLIAdapter implements EngineAdapter {
129
129
  const binary = resolveBinary("claude", [
130
130
  "~/.local/bin/claude",
131
131
  "/usr/local/bin/claude",
132
+ "/opt/homebrew/bin/claude",
132
133
  ]);
133
134
 
134
- const args = ["-p"];
135
+ const args = [
136
+ "-p",
137
+ "--dangerously-skip-permissions",
138
+ "--no-session-persistence",
139
+ ];
135
140
  if (model && model !== "claude" && model !== "default") {
136
141
  args.push("--model", model);
137
142
  }
@@ -155,8 +160,9 @@ export class OpenCodeCLIAdapter implements EngineAdapter {
155
160
  if (model && model !== "opencode" && model !== "default") {
156
161
  args.push("-m", model);
157
162
  }
163
+ args.push(prompt);
158
164
 
159
- const rawOutput = await runCliWithStdin(binary, args, prompt, "OpenCode");
165
+ const rawOutput = await runCliWithStdin(binary, args, "", "OpenCode");
160
166
 
161
167
  // OpenCode prints progress lines starting with "> build ·" or similar.
162
168
  // Filter these out to get a clean result.
@@ -212,28 +218,78 @@ export class CodexCLIAdapter implements EngineAdapter {
212
218
  }
213
219
  }
214
220
 
221
+ // ─── Codex Fugu CLI Adapter ────────────────────────────────────────────────────
222
+
223
+ export class CodexFuguCLIAdapter implements EngineAdapter {
224
+ readonly name = "codex-fugu";
225
+
226
+ async run(prompt: string, model: string): Promise<string> {
227
+ const binary = resolveBinary("codex-fugu", [
228
+ "~/.local/bin/codex-fugu",
229
+ "/usr/local/bin/codex-fugu",
230
+ "/opt/homebrew/bin/codex-fugu",
231
+ ]);
232
+
233
+ const tempFile = path.join(
234
+ process.cwd(),
235
+ `.codex-fugu-out-${crypto.randomBytes(6).toString("hex")}.txt`
236
+ );
237
+
238
+ const args = [
239
+ "--dangerously-bypass-approvals-and-sandbox",
240
+ "exec",
241
+ "-",
242
+ "-o",
243
+ tempFile,
244
+ ];
245
+ if (model && model !== "codex-fugu" && model !== "default") {
246
+ args.push("-m", model);
247
+ }
248
+
249
+ try {
250
+ await runCliWithStdin(binary, args, prompt, "Codex Fugu");
251
+ if (!fs.existsSync(tempFile)) {
252
+ throw new Error(`Codex Fugu execution succeeded but output file was not created at ${tempFile}`);
253
+ }
254
+ const output = fs.readFileSync(tempFile, "utf8");
255
+ return output;
256
+ } finally {
257
+ if (fs.existsSync(tempFile)) {
258
+ fs.unlinkSync(tempFile);
259
+ }
260
+ }
261
+ }
262
+ }
263
+
215
264
  // ─── Antigravity CLI Adapter (agy) ────────────────────────────────────────────
216
265
  // Google Antigravity CLI — binary: `agy`
217
266
  // Docs: https://antigravity.google/docs/cli-using
218
267
  // Headless: `agy -p "<prompt>"` or via stdin with `agy -p`
219
268
 
220
269
  export class AgyCLIAdapter implements EngineAdapter {
221
- readonly name = "agy";
270
+ readonly name: string;
271
+
272
+ constructor(name: string = "agy") {
273
+ this.name = name;
274
+ }
222
275
 
223
276
  async run(prompt: string, model: string): Promise<string> {
224
- const binary = resolveBinary("agy", [
225
- "~/.local/bin/agy",
226
- "/usr/local/bin/agy",
227
- "/opt/homebrew/bin/agy",
277
+ const binary = resolveBinary(this.name, [
278
+ `~/.local/bin/${this.name}`,
279
+ `/usr/local/bin/${this.name}`,
280
+ `/opt/homebrew/bin/${this.name}`,
228
281
  ]);
229
282
 
230
- const args: string[] = [];
231
- if (model && model !== "agy" && model !== "default") {
232
- args.push("--model", model);
283
+ const args: string[] = [
284
+ "--dangerously-skip-permissions",
285
+ "-p",
286
+ prompt,
287
+ ];
288
+ if (model && model !== this.name && model !== "agy" && model !== "default") {
289
+ args.unshift("--model", model);
233
290
  }
234
- args.push("-p", "-");
235
291
 
236
- return await runCliWithStdin(binary, args, prompt, "Antigravity (agy)");
292
+ return await runCliWithStdin(binary, args, "", `Antigravity (${this.name})`);
237
293
  }
238
294
  }
239
295
 
@@ -311,8 +367,11 @@ export function getAdapter(
311
367
  if (engine === "codex") {
312
368
  return new CodexCLIAdapter();
313
369
  }
314
- if (engine === "agy" || engine === "antigravity") {
315
- return new AgyCLIAdapter();
370
+ if (engine === "codex-fugu" || engine === "codexfugu" || engine === "fugu") {
371
+ return new CodexFuguCLIAdapter();
372
+ }
373
+ if (engine === "agy" || engine === "antigravity" || /^agy\d+$/.test(engine)) {
374
+ return new AgyCLIAdapter(engine === "antigravity" ? "agy" : engine);
316
375
  }
317
376
  if (engine === "qwen" || engine === "qwencode" || engine === "qwen-code") {
318
377
  return new QwenCodeCLIAdapter();
@@ -325,6 +384,9 @@ export function getAdapter(
325
384
  if (engine.startsWith("claude")) {
326
385
  return new ClaudeCodeCLIAdapter();
327
386
  }
387
+ if (engine.startsWith("codex-fugu") || engine.startsWith("fugu")) {
388
+ return new CodexFuguCLIAdapter();
389
+ }
328
390
  if (engine.startsWith("gpt") || engine.startsWith("o1") || engine.startsWith("o3") || engine.startsWith("o4")) {
329
391
  return new CodexCLIAdapter();
330
392
  }
@@ -364,6 +426,9 @@ export function getAdapter(
364
426
  if (binaryExists("claude", ["~/.local/bin/claude"])) {
365
427
  return new ClaudeCodeCLIAdapter();
366
428
  }
429
+ if (binaryExists("codex-fugu", ["~/.local/bin/codex-fugu", "/opt/homebrew/bin/codex-fugu"])) {
430
+ return new CodexFuguCLIAdapter();
431
+ }
367
432
  if (binaryExists("codex", ["~/Library/PhpWebStudy/env/node/bin/codex"])) {
368
433
  return new CodexCLIAdapter();
369
434
  }
@@ -379,7 +444,7 @@ export function getAdapter(
379
444
 
380
445
  throw new Error(
381
446
  `❌ No active CLI agent engine could be auto-detected.\n` +
382
- ` Supported engines: claude, opencode, codex, agy, qwen, mimo\n` +
447
+ ` Supported engines: claude, opencode, codex, codex-fugu, agy, qwen, mimo\n` +
383
448
  ` Install one and re-run, or specify with: --engine <engine-name>\n` +
384
449
  ` Run "imhcode --help" for more information.`
385
450
  );
@@ -39,6 +39,7 @@ export {
39
39
  ClaudeCodeCLIAdapter,
40
40
  OpenCodeCLIAdapter,
41
41
  CodexCLIAdapter,
42
+ CodexFuguCLIAdapter,
42
43
  AgyCLIAdapter,
43
44
  QwenCodeCLIAdapter,
44
45
  MimoCodeCLIAdapter,
@@ -121,12 +122,12 @@ const DEFAULT_MODEL_PREFERENCES: Record<string, { engines: string[]; models: str
121
122
  models: ["claude-opus-4-6", "gpt-5.5", "deepseek-v4-pro"],
122
123
  },
123
124
  testing: {
124
- engines: ["codex", "claude", "opencode"],
125
- models: ["gpt-5.5", "claude-opus-4-6", "deepseek-v4-pro"],
125
+ engines: ["codex-fugu", "codex", "claude", "opencode"],
126
+ models: ["fugu-ultra", "gpt-5.5", "claude-opus-4-6", "deepseek-v4-pro"],
126
127
  },
127
128
  review: {
128
- engines: ["codex", "claude", "opencode"],
129
- models: ["gpt-5.5", "claude-sonnet-4-5", "deepseek-v4"],
129
+ engines: ["codex-fugu", "codex", "claude", "opencode"],
130
+ models: ["fugu-ultra", "gpt-5.5", "claude-sonnet-4-5", "deepseek-v4"],
130
131
  },
131
132
  fast: {
132
133
  engines: ["opencode", "codex", "qwen", "claude"],
@@ -295,6 +296,7 @@ export async function runAgent(
295
296
  if (resolvedEngine === "antigravity" || resolvedEngine === "antigravity-ide") resolvedEngine = "agy";
296
297
  if (resolvedEngine === "qwencode" || resolvedEngine === "qwen-code") resolvedEngine = "qwen";
297
298
  if (resolvedEngine === "mimocode" || resolvedEngine === "mimo-code") resolvedEngine = "mimo";
299
+ if (resolvedEngine === "codexfugu" || resolvedEngine === "fugu") resolvedEngine = "codex-fugu";
298
300
 
299
301
  // 2. Build the initial prompt (category-aware, compact context)
300
302
  const finalPrompt = buildPrompt(agent, task, acceptanceCriteria, cwd);
@@ -305,9 +307,11 @@ export async function runAgent(
305
307
  // 3. Load config for routing checks
306
308
  const config = loadConfig(cwd);
307
309
  const category = AGENT_CATEGORY_MAP[agent.manifest.id] ?? "backend";
310
+ const complexity = detectTaskComplexity(task);
311
+ const effectiveCategory = complexity === "light" ? "fast" : category;
308
312
 
309
313
  // 4. Build the failover queue of engines to try
310
- const failoverQueue = getFailoverQueue(agent, config, resolvedEngine);
314
+ const failoverQueue = getFailoverQueue(agent, config, resolvedEngine, effectiveCategory);
311
315
 
312
316
  let currentEngineIndex = 0;
313
317
  let currentEngine = resolvedEngine;
@@ -320,7 +324,7 @@ export async function runAgent(
320
324
  while (currentEngineIndex < failoverQueue.length) {
321
325
  currentEngine = failoverQueue[currentEngineIndex];
322
326
  if (currentEngineIndex > 0) {
323
- currentModel = getModelForEngine(config, currentEngine, category, agent);
327
+ currentModel = getModelForEngine(config, currentEngine, effectiveCategory, agent);
324
328
  } else {
325
329
  currentModel = resolvedModel;
326
330
  }
@@ -475,15 +479,27 @@ function getInstalledEngines(config: any): string[] {
475
479
  /**
476
480
  * Builds the ordered array of fallback engines to attempt during execution.
477
481
  */
478
- function getFailoverQueue(
482
+ export function getFailoverQueue(
479
483
  agent: LoadedAgent,
480
484
  config: any,
481
- initialEngine: string
485
+ initialEngine: string,
486
+ category: string
482
487
  ): string[] {
483
488
  const installed = getInstalledEngines(config);
484
489
  const queue = [initialEngine];
485
490
 
486
- // 1. Add preferred engines from manifest
491
+ // 1. Add category preferred engines from DEFAULT_MODEL_PREFERENCES
492
+ const prefs = DEFAULT_MODEL_PREFERENCES[category];
493
+ if (prefs) {
494
+ for (const eng of prefs.engines) {
495
+ const clean = eng.toLowerCase().trim();
496
+ if (!queue.includes(clean) && installed.includes(clean)) {
497
+ queue.push(clean);
498
+ }
499
+ }
500
+ }
501
+
502
+ // 2. Add preferred engines from manifest
487
503
  if (agent.manifest.preferred_engines) {
488
504
  for (const eng of agent.manifest.preferred_engines) {
489
505
  const clean = eng.toLowerCase().trim();
@@ -493,7 +509,7 @@ function getFailoverQueue(
493
509
  }
494
510
  }
495
511
 
496
- // 2. Add fallback engines from manifest
512
+ // 3. Add fallback engines from manifest
497
513
  if (agent.manifest.fallback_engines) {
498
514
  for (const eng of agent.manifest.fallback_engines) {
499
515
  const clean = eng.toLowerCase().trim();
@@ -503,20 +519,36 @@ function getFailoverQueue(
503
519
  }
504
520
  }
505
521
 
506
- // 3. Add any other installed engines as a last resort
522
+ // 4. Add any other installed engines as a last resort
507
523
  for (const eng of installed) {
508
524
  if (!queue.includes(eng)) {
509
525
  queue.push(eng);
510
526
  }
511
527
  }
512
528
 
513
- return queue;
529
+ // 5. Expand agy to include agy2, agy3, agy4... etc. right after agy
530
+ const expandedQueue: string[] = [];
531
+ for (const eng of queue) {
532
+ if (!expandedQueue.includes(eng)) {
533
+ expandedQueue.push(eng);
534
+ }
535
+ if (eng === "agy" || eng === "antigravity") {
536
+ for (let i = 2; i <= 10; i++) {
537
+ const key = `agy${i}`;
538
+ if (installed.includes(key) && !expandedQueue.includes(key)) {
539
+ expandedQueue.push(key);
540
+ }
541
+ }
542
+ }
543
+ }
544
+
545
+ return expandedQueue;
514
546
  }
515
547
 
516
548
  /**
517
549
  * Resolves the model to use for a failover engine.
518
550
  */
519
- function getModelForEngine(
551
+ export function getModelForEngine(
520
552
  config: any,
521
553
  engineName: string,
522
554
  category: string,
@@ -527,6 +559,23 @@ function getModelForEngine(
527
559
  return config.model_routing[category].model;
528
560
  }
529
561
 
562
+ // Check if there is a category preferred model for this engine in DEFAULT_MODEL_PREFERENCES
563
+ const lookupEngine = (engineName.startsWith("agy") || engineName === "antigravity") ? "agy" : engineName;
564
+ const prefs = DEFAULT_MODEL_PREFERENCES[category];
565
+ if (prefs) {
566
+ const engineIdx = prefs.engines.indexOf(lookupEngine);
567
+ if (engineIdx >= 0) {
568
+ const preferredMdl = prefs.models[engineIdx];
569
+ const engineData = config?.available_engines?.[engineName];
570
+ if (engineData?.models?.length > 0) {
571
+ const exactMatch = engineData.models.find((m: string) =>
572
+ m.toLowerCase().includes(preferredMdl.toLowerCase().replace(/-/g, ""))
573
+ );
574
+ if (exactMatch) return exactMatch;
575
+ }
576
+ }
577
+ }
578
+
530
579
  // Check available models in config for this engine
531
580
  const engineData = config?.available_engines?.[engineName];
532
581
  if (engineData?.models?.length > 0) {
@@ -67,7 +67,7 @@ export interface LoadedAgent {
67
67
  // ─── Execution ────────────────────────────────────────────────────────────────
68
68
 
69
69
  export interface ExecutionOptions {
70
- /** Override the CLI engine (claude, opencode, codex, antigravity) */
70
+ /** Override the CLI engine (claude, opencode, codex, codex-fugu, antigravity) */
71
71
  engine?: string;
72
72
  /** Explicitly override the model (e.g. gpt-4o, gemini-3.5-flash) */
73
73
  model?: string;