hankweave 0.5.7 → 0.6.2

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.
Files changed (79) hide show
  1. package/README.md +12 -11
  2. package/dist/base-process-manager.d.ts +30 -0
  3. package/dist/budget.d.ts +315 -0
  4. package/dist/checkpoint-git.d.ts +98 -0
  5. package/dist/claude-agent-sdk-manager.d.ts +144 -0
  6. package/dist/claude-log-parser.d.ts +63 -0
  7. package/dist/claude-runtime-extractor.d.ts +73 -0
  8. package/dist/codex-runtime-extractor.d.ts +107 -0
  9. package/dist/codon-runner.d.ts +278 -0
  10. package/dist/config-validation/model-validator.d.ts +16 -0
  11. package/dist/config-validation/sentinel.schema.d.ts +6967 -0
  12. package/dist/config.d.ts +40815 -0
  13. package/dist/cost-tracker.d.ts +72 -0
  14. package/dist/execution-planner.d.ts +62 -0
  15. package/dist/execution-thread.d.ts +71 -0
  16. package/dist/exports/schemas.d.ts +9 -0
  17. package/dist/exports/schemas.js +1019 -0
  18. package/dist/exports/types.d.ts +15 -0
  19. package/dist/exports/types.js +60 -0
  20. package/dist/file-resolver.d.ts +33 -0
  21. package/dist/index.js +380 -293
  22. package/dist/index.js.map +33 -29
  23. package/dist/llm/llm-provider-registry.d.ts +207 -0
  24. package/dist/llm/models-dev-schema.d.ts +679 -0
  25. package/dist/llm/provider-config.d.ts +30 -0
  26. package/dist/prompt-builder.d.ts +75 -0
  27. package/dist/prompt-frontmatter.d.ts +61 -0
  28. package/dist/replay-process-manager.d.ts +82 -0
  29. package/dist/runtime-extractor-base.d.ts +120 -0
  30. package/dist/schemas/event-schemas.d.ts +8389 -0
  31. package/dist/schemas/websocket-log-schemas.d.ts +4502 -0
  32. package/dist/shim-process-manager.d.ts +98 -0
  33. package/dist/shim-runtime-extractor.d.ts +51 -0
  34. package/dist/shims/codex/README.md +129 -0
  35. package/dist/shims/codex/THIRDPARTY.md +18 -0
  36. package/dist/shims/codex/VERSION +1 -0
  37. package/dist/shims/codex/common/package.json +24 -0
  38. package/dist/shims/codex/index.js +1154 -970
  39. package/dist/shims/codex/package.json +46 -0
  40. package/dist/shims/codex/tsup.config.ts +16 -0
  41. package/dist/shims/gemini/README.md +59 -0
  42. package/dist/shims/gemini/THIRDPARTY.md +32 -0
  43. package/dist/shims/gemini/VERSION +1 -0
  44. package/dist/shims/gemini/common/package.json +24 -0
  45. package/dist/shims/gemini/index.js +1359 -30
  46. package/dist/shims/gemini/package.json +37 -0
  47. package/dist/shims/opencode/README.md +82 -0
  48. package/dist/shims/opencode/THIRDPARTY.md +32 -0
  49. package/dist/shims/opencode/VERSION +1 -0
  50. package/dist/shims/opencode/common/package.json +24 -0
  51. package/dist/shims/opencode/index.js +1476 -0
  52. package/dist/shims/opencode/package.json +38 -0
  53. package/dist/shims/pi/README.md +87 -0
  54. package/dist/shims/pi/THIRDPARTY.md +24 -0
  55. package/dist/shims/pi/VERSION +1 -0
  56. package/dist/shims/pi/common/package.json +24 -0
  57. package/dist/shims/pi/index.js +249832 -0
  58. package/dist/shims/pi/package.json +53 -0
  59. package/dist/state-manager.d.ts +161 -0
  60. package/dist/state-transition-guards.d.ts +37 -0
  61. package/dist/telemetry/telemetry-types.d.ts +206 -0
  62. package/dist/typed-event-emitter.d.ts +57 -0
  63. package/dist/types/branded-types.d.ts +15 -0
  64. package/dist/types/budget-types.d.ts +82 -0
  65. package/dist/types/claude-session-schema.d.ts +2430 -0
  66. package/dist/types/error-types.d.ts +44 -0
  67. package/dist/types/input-ai-types.d.ts +1070 -0
  68. package/dist/types/llm-call-types.d.ts +3829 -0
  69. package/dist/types/sentinel-types.d.ts +66 -0
  70. package/dist/types/state-types.d.ts +1099 -0
  71. package/dist/types/tool-types.d.ts +86 -0
  72. package/dist/types/types.d.ts +367 -0
  73. package/dist/types/websocket-log-types.d.ts +7 -0
  74. package/dist/utils.d.ts +452 -0
  75. package/package.json +15 -2
  76. package/schemas/hank.schema.json +158 -3
  77. package/schemas/hankweave.schema.json +17 -1
  78. package/shims/codex/index.js +0 -1583
  79. package/shims/gemini/index.js +0 -31
@@ -1,31 +1,1360 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as pe}from"fs";import{dirname as ge,join as fe}from"path";import{fileURLToPath as he}from"url";var Y=["none","standard","strict"];function J(s,e){let t={model:"",verbose:!1,idleTimeout:120,sandbox:"none",selfTest:!1,version:!1,help:!1};for(let r=0;r<s.length;r++){let i=s[r];if(i.includes("=")){let[n,l]=i.split("=",2);s.splice(r,1,n,l),i=n}switch(e&&i in e&&(i=e[i]),i){case"--model":t.model=s[++r];break;case"--resume":t.resume=s[++r];break;case"--verbose":t.verbose=!0;break;case"--append-system-prompt":t.appendSystemPrompt=s[++r];break;case"--debug-dir":t.debugDir=s[++r];break;case"--idle-timeout":{let n=Number(s[++r]);(!Number.isFinite(n)||n<=0)&&(console.error("Invalid --idle-timeout value: must be a positive number"),process.exit(1)),t.idleTimeout=n;break}case"--sandbox":{let n=s[++r];Y.includes(n)||(console.error(`Invalid --sandbox value: must be one of ${Y.join(", ")}`),process.exit(1)),t.sandbox=n;break}case"--self-test":t.selfTest=!0;break;case"--version":t.version=!0;break;case"--help":t.help=!0;break}}return t}import{spawn as A}from"child_process";import{createWriteStream as O}from"fs";import{mkdir as z}from"fs/promises";import{isAbsolute as G,resolve as h}from"path";import{createInterface as le}from"readline";var _=class{constructor(e){this.options=e;this.verbose=e.verbose}process=null;verbose;rawEventStream=null;stderrStream=null;stderrBuffer="";currentSessionId="unknown";static async isInstalled(){return new Promise(e=>{let t=process.platform==="win32",i=A(t?"where":"which",["gemini"],{shell:t}),n="";i.stdout?.on("data",l=>{n+=l.toString()}),i.on("close",l=>{if(l===0&&n.trim()){let c=A("gemini",["--version"],{shell:t}),o="";c.stdout?.on("data",m=>{o+=m.toString()}),c.on("close",()=>{e({found:!0,path:n.trim(),version:o.trim()})})}else e({found:!1})})})}async initializeDebugStreams(e){if(this.options.debugDir)try{let t=G(this.options.debugDir)?this.options.debugDir:h(this.options.cwd,this.options.debugDir);await z(t,{recursive:!0});let r=h(t,`session-${e}.raw.jsonl`);this.rawEventStream=O(r,{flags:"a",encoding:"utf8"});let i=h(t,`session-${e}.raw.log`);this.stderrStream=O(i,{flags:"a",encoding:"utf8"}),this.stderrBuffer&&(this.stderrStream.write(this.stderrBuffer),this.stderrBuffer=""),this.currentSessionId=e}catch(t){throw new Error(`Failed to create debug log files in ${this.options.debugDir}: ${t instanceof Error?t.message:String(t)}`)}}async renameDebugStreams(e){if(!(!this.options.debugDir||this.currentSessionId===e))try{let t=G(this.options.debugDir)?this.options.debugDir:h(this.options.cwd,this.options.debugDir);await this.closeDebugStreams();let{rename:r}=await import("fs/promises"),i=h(t,`session-${this.currentSessionId}.raw.jsonl`),n=h(t,`session-${e}.raw.jsonl`),l=h(t,`session-${this.currentSessionId}.raw.log`),c=h(t,`session-${e}.raw.log`);try{await r(i,n)}catch(o){this.verbose&&console.error(`[gemini-cli-shim] Could not rename ${i}:`,o)}try{await r(l,c)}catch(o){this.verbose&&console.error(`[gemini-cli-shim] Could not rename ${l}:`,o)}await this.initializeDebugStreams(e)}catch(t){throw new Error(`Failed to rename debug log files: ${t instanceof Error?t.message:String(t)}`)}}writeRawEvent(e){this.rawEventStream&&this.rawEventStream.write(`${JSON.stringify(e)}
3
- `)}writeStderr(e){this.options.debugDir&&(this.stderrStream?this.stderrStream.write(e):this.stderrBuffer+=e)}async closeDebugStreams(){let e=[];if(this.stderrBuffer&&!this.stderrStream&&this.options.debugDir)try{let t=G(this.options.debugDir)?this.options.debugDir:h(this.options.cwd,this.options.debugDir);await z(t,{recursive:!0});let r=h(t,`session-${this.currentSessionId}.raw.log`),i=O(r,{flags:"a",encoding:"utf8"});await new Promise((n,l)=>{i.write(this.stderrBuffer,c=>{c?l(c):i.end(o=>{o?l(o):n()})})}),this.stderrBuffer=""}catch(t){console.error("[gemini-cli-shim] Error flushing buffered stderr:",t)}this.rawEventStream&&(e.push(new Promise((t,r)=>{this.rawEventStream?.end(i=>{i?r(i):t()})})),this.rawEventStream=null),this.stderrStream&&(e.push(new Promise((t,r)=>{this.stderrStream?.end(i=>{i?r(i):t()})})),this.stderrStream=null),await Promise.all(e)}async spawn(e){let t=["--model",this.options.model,"--output-format","stream-json","--yolo"];this.options.sandbox!=="none"&&t.push("--sandbox"),this.options.resume&&t.push("--resume",this.options.resume),this.verbose&&console.error("[gemini-cli-shim] Spawning gemini:",t.join(" "));let r=process.platform==="win32";this.process=A("gemini",t,{cwd:this.options.cwd,stdio:["pipe","pipe","pipe"],env:{...process.env,...this.options.sandbox==="strict"&&{SEATBELT_PROFILE:"strict-open"}},shell:r}),this.options.debugDir&&await this.initializeDebugStreams("unknown");let i=this.options.appendSystemPrompt?`${e}
4
-
5
- Additional instructions: ${this.options.appendSystemPrompt}`:e;return this.process.stdin?.write(i),this.process.stdin?.end(),this.createEventStream()}async*createEventStream(){if(!this.process||!this.process.stdout||!this.process.stderr)throw new Error("Process not spawned");let e="",t=!1,r=!1,i="",n=null;this.process.stderr.on("data",c=>{let o=c.toString();e+=o,this.writeStderr(o),!r&&(o.includes("Error resuming session:")||o.includes("Invalid session identifier"))&&(t=!0,r=!0,this.verbose&&console.error("[gemini-cli-shim] Session error detected, killing process"),this.process&&this.process.kill("SIGKILL")),this.verbose&&console.error("[gemini stderr]",o)});let l=le({input:this.process.stdout,crlfDelay:1/0});if(t)throw new Error("Invalid session ID");try{for await(let c of l){let o=c.trim();if(t)throw new Error("Invalid session ID");if(o)try{let m=JSON.parse(o);m.type==="init"&&m.session_id&&(i=m.session_id,this.options.debugDir&&this.currentSessionId==="unknown"&&await this.renameDebugStreams(i)),this.writeRawEvent(m),this.verbose&&console.error("[gemini event]",JSON.stringify(m)),yield m}catch(m){this.verbose&&(console.error("[gemini-cli-shim] Failed to parse line:",o),console.error("[gemini-cli-shim] Error:",m))}}}catch(c){throw t?new Error("Invalid session ID"):c}if(t)throw new Error("Invalid session ID");await new Promise((c,o)=>{if(!this.process){t?o(new Error("Invalid session ID")):c();return}let m=setTimeout(()=>{this.verbose&&console.error("[gemini-cli-shim] Process exit timeout, forcing kill"),this.kill(),o(t?new Error("Invalid session ID"):new Error("Process did not exit within timeout"))},2e3);this.process.on("close",async d=>{clearTimeout(m),n=d;try{await this.closeDebugStreams()}catch(I){console.error("[gemini-cli-shim] Error closing debug streams:",I)}t?o(new Error("Invalid session ID")):d===0||d===null?c():o(new Error(`Gemini CLI exited with code ${d}`))}),this.process.on("error",async d=>{clearTimeout(m);try{await this.closeDebugStreams()}catch(I){console.error("[gemini-cli-shim] Error closing debug streams:",I)}o(t?new Error("Invalid session ID"):d)})})}kill(){this.process&&(this.process.kill("SIGTERM"),this.process=null)}};import{readFileSync as ue}from"fs";import{resolve as de}from"path";import{randomUUID as Le}from"crypto";import Ne from"fs";import Ce from"path";var L=class extends Error{timeoutMs;constructor(e){super(`Idle timeout: no events received for ${e}ms`),this.name="IdleTimeoutError",this.timeoutMs=e}};async function*q(s,e){let t=s[Symbol.asyncIterator]();try{for(;;){let r;try{let i=await Promise.race([t.next(),new Promise((n,l)=>{r=setTimeout(()=>{l(new L(e))},e)})]);if(i.done)break;yield i.value}finally{clearTimeout(r)}}}finally{t.return?.()}}var H=["Read","Write","Edit","Bash","Glob","Grep","LS"];var V={sonnet:"anthropic/claude-sonnet-4-20250514",haiku:"anthropic/claude-3-haiku",opus:"anthropic/claude-opus-4-5-20251101",flash:"google/gemini-2.0-flash",pro:"google/gemini-2.0-pro"};function Z(s){if(!s){let e=process.env.MODEL;e?s=e:s="flash"}if(V[s]){let e=V[s];return e.startsWith("google/")?e.replace("google/",""):e}if(s.includes("/")){let[e,t]=s.split("/",2);return e==="google"?t:s}return s}function E(s){return s.startsWith("anthropic/")?s.replace("anthropic/",""):!s.includes("/")&&(s.startsWith("gemini-")||s==="flash"||s==="pro")?`google/${s}`:s}import{randomBytes as X}from"crypto";function R(){let s=Date.now().toString(36),e=X(5).toString("hex");return`msg_${s}${e}`}function N(){let s=Date.now().toString(36),e=X(6).toString("hex");return`toolu_${s}${e}`}var x="00000000-0000-0000-0000-000000000000";var ce={read_file:"Read",readFile:"Read",file_read:"Read",write_file:"Write",writeFile:"Write",file_write:"Write",edit_file:"Edit",editFile:"Edit",str_replace_editor:"Edit",run_shell_command:"Bash",bash:"Bash",shell:"Bash",execute_bash:"Bash",list_directory:"LS",ls:"LS",list:"LS",list_dir:"LS",glob:"Glob",find_files:"Glob",grep:"Grep",search_files:"Grep",search:"Grep"};function Q(s){return ce[s]||s}function me(s){return s.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}function ee(s){if(!s)return s;let e={};for(let[t,r]of Object.entries(s))e[me(t)]=r;return e}function k(){return[...H]}function y(s){console.log(JSON.stringify(s))}function $(){return process.env.GOOGLE_API_KEY?"GOOGLE_API_KEY":process.env.GEMINI_API_KEY?"GEMINI_API_KEY":"none"}async function se(s,e){let t=Date.now(),r=0,i=0,n="",l=0,c={input_tokens:0,output_tokens:0},o=!1,m="",d=!1;if(e.debugDir)try{let{mkdirSync:p}=await import("fs");p(e.debugDir,{recursive:!0})}catch{}let I="Always repeat the results of your tool calls (like file contents or command output) in your text response. This is critical for verification.",ie=e.appendSystemPrompt?`${I}
6
- ${e.appendSystemPrompt}`:I,oe={...e,appendSystemPrompt:ie},C=new Set,U=new Map,B=new Map,w=[],T=R(),D=!1,b="";try{let P=await new _(oe).spawn(s),K=q(P,e.idleTimeout*1e3);for await(let a of K)switch(a.type){case"init":{n=a.session_id,d=!0;let u={type:"system",subtype:"init",cwd:e.cwd,session_id:n,tools:k(),model:E(e.model),permissionMode:"bypassPermissions",apiKeySource:$(),mcp_servers:[]};y(u),r=Date.now();break}case"message":{if(a.role==="assistant"){if(a.delta)D=!0,b+=a.content,w.push({type:"text",text:a.content});else if(!D)w.push({type:"text",text:a.content}),b=a.content;else if(a.content.length>b.length&&a.content.startsWith(b)){let u=a.content.slice(b.length);u.trim()&&(w.push({type:"text",text:u}),b=a.content)}}break}case"tool_use":{if(!C.has(a.tool_id)){C.add(a.tool_id);let u=N();U.set(a.tool_id,u);let v=Q(a.tool_name),S=ee(a.parameters);B.set(a.tool_id,{name:v,params:a.parameters}),w.push({type:"tool_use",id:u,name:v,input:S});let j={type:"assistant",message:{id:T,type:"message",role:"assistant",model:E(e.model),content:w,stop_reason:"tool_use"}};y(j),w=[],T=R(),D=!1,b="",l++}break}case"tool_result":{let u=U.get(a.tool_id)||N(),v=B.get(a.tool_id),S="";if(a.status==="error")S={is_error:!0,error:a.error||"Unknown error"};else{let F=a;if(S=a.output!==void 0?a.output:F.result||F.content||"",v?.name==="Read"&&S===""){let M=v.params?.file_path||v.params?.filePath||v.params?.path;if(M&&typeof M=="string")try{let W=de(e.cwd,M);S=ue(W,"utf-8")}catch{}}}y({type:"user",message:{role:"user",content:[{type:"tool_result",tool_use_id:u,content:S}]}}),D=!1,b="";break}case"result":{if(i=Date.now(),w.length>0){let u={type:"assistant",message:{id:T,type:"message",role:"assistant",model:E(e.model),content:w,stop_reason:"end_turn"}};y(u),l++}c.input_tokens=(c.input_tokens||0)+(a.stats.input_tokens||0),c.output_tokens=(c.output_tokens||0)+(a.stats.output_tokens||0),o=a.status==="error",m=o?"Error completing request":"Request completed successfully";break}}}catch(p){let P=p instanceof Error?p.message:String(p);if(P.includes("Invalid session ID"))throw p;if(o=!0,m=`Agent Error: ${P}`,!d){let a={type:"system",subtype:"init",cwd:e.cwd,session_id:n||x,tools:k(),model:E(e.model),permissionMode:"bypassPermissions",apiKeySource:$(),mcp_servers:[]};y(a),d=!0}y({type:"assistant",message:{id:x,type:"message",role:"assistant",model:"<synthetic>",content:[{type:"text",text:m}]}})}let ne=Date.now(),ae={type:"result",subtype:o?"error":"success",is_error:o,duration_ms:ne-t,duration_api_ms:i>0?i-r:0,num_turns:l,result:m,session_id:n,usage:c};return y(ae),await new Promise(p=>{process.stdout.write("",()=>p())}),{exitCode:o?1:0,sessionId:n}}async function te(s,e){let t={type:"system",subtype:"init",cwd:process.cwd(),session_id:e||x,tools:k(),model:"<unknown>",permissionMode:"bypassPermissions",apiKeySource:$(),mcp_servers:[]};y(t);let r={type:"assistant",message:{id:x,type:"message",role:"assistant",model:"<synthetic>",content:[{type:"text",text:`API Error: ${s}`}]}};y(r),y({type:"result",subtype:"error",is_error:!0,duration_ms:0,duration_api_ms:0,num_turns:0,result:s,session_id:e}),await new Promise(n=>{process.stdout.write("",()=>n())})}var ye=he(import.meta.url),we=ge(ye);async function be(){let s=[];for await(let e of process.stdin)s.push(Buffer.from(e));return Buffer.concat(s).toString("utf-8").trim()}function re(){console.error(`
7
- Gemini CLI Shim - Translate Gemini CLI to standardized JSONL output
8
-
9
- Usage: gemini-cli-shim [options]
10
-
11
- Required Arguments:
12
- --model <model> Model identifier (shortname, provider/model, or full ID)
13
-
14
- Optional Arguments:
15
- -p Indicates prompt via stdin (optional, stdin always read)
16
- --resume <session_id> Session ID to continue
17
- --verbose Enable verbose logging to stderr
18
- --append-system-prompt <txt> Additional system prompt to append
19
- --idle-timeout <seconds> Max seconds between agent events before aborting (default: 120)
20
- --debug-dir <path> Directory for debug logs and session data
21
- --sandbox <level> Sandbox level: none (default), standard, or strict
22
- --self-test Run environment verification
23
- --version Print version and exit
24
- --help Print this help and exit
25
-
26
- Examples:
27
- echo "Hello" | gemini-cli-shim --model flash
28
- echo "Continue" | gemini-cli-shim --model pro --resume <session_id>
29
- echo "Debug" | gemini-cli-shim --model flash --debug-dir ./debug
30
- gemini-cli-shim --self-test
31
- `)}function ve(){try{let s=fe(we,"..","package.json"),e=JSON.parse(pe(s,"utf-8"));console.error(`gemini-cli-shim v${e.version}`)}catch{console.error("gemini-cli-shim (version unknown)")}}async function Se(){let s=[],e=await _.isInstalled();s.push({name:"gemini_cli_found",passed:e.found,message:e.found?`Gemini CLI found at ${e.path} (${e.version})`:"Gemini CLI not found in PATH"});let t=!!(process.env.GOOGLE_API_KEY||process.env.GEMINI_API_KEY);s.push({name:"api_key",passed:t,message:t?"API key found in environment":"No GOOGLE_API_KEY or GEMINI_API_KEY found"});let r=s.every(n=>n.passed),i={shim:{name:"gemini-cli-shim",version:"1.0.0"},agent:{name:"gemini-cli",version:e.version||"unknown",found:e.found},checks:s,overall:{passed:r,message:r?"All checks passed":"Some checks failed"}};console.log(JSON.stringify(i,null,2)),process.exit(r?0:1)}async function _e(){let s=J(process.argv.slice(2),{"-m":"--model","-r":"--resume","-v":"--verbose","-h":"--help"});if(s.help&&(re(),process.exit(0)),s.version&&(ve(),process.exit(0)),s.selfTest){await Se();return}s.model||(console.error("Error: --model is required"),re(),process.exit(1));let e=await be();e||process.exit(0);let t=Z(s.model);(await _.isInstalled()).found||(console.error("Error: Gemini CLI not found in PATH"),console.error("Please install Gemini CLI: https://geminicli.com/docs/"),process.exit(1)),!process.env.GOOGLE_API_KEY&&!process.env.GEMINI_API_KEY&&(console.error("Error: No API key found"),console.error("Set GOOGLE_API_KEY or GEMINI_API_KEY environment variable"),process.exit(1));let i=!1,n=()=>{i||(i=!0,s.verbose&&console.error("[gemini-cli-shim] Received interrupt signal, exiting gracefully"),process.exit(0))};process.on("SIGINT",n),process.on("SIGTERM",n);try{let l=await se(e,{model:t,resume:s.resume,verbose:s.verbose,cwd:process.cwd(),appendSystemPrompt:s.appendSystemPrompt,debugDir:s.debugDir,idleTimeout:s.idleTimeout,sandbox:s.sandbox});process.exit(l.exitCode)}catch(l){let c=l instanceof Error?l.message:String(l);c.includes("Invalid session ID")&&(console.error(`Error: Session not found: ${s.resume}`),console.error("Use a valid session ID to resume"),process.exit(1)),await te(c),process.exit(1)}}_e().catch(s=>{console.error("Fatal error:",s),process.exit(1)});
2
+
3
+ // src/index.ts
4
+ import process3 from "node:process";
5
+
6
+ // src/args.ts
7
+ function parseArgs(argv) {
8
+ const args = {
9
+ model: process.env.MODEL || "gemini-2.5-flash",
10
+ verbose: false,
11
+ idleTimeout: 120,
12
+ sandbox: "none",
13
+ selfTest: false,
14
+ version: false,
15
+ help: false
16
+ };
17
+ const takeValue = (index, current) => {
18
+ if (current.includes("=")) {
19
+ const [, value] = current.split(/=(.*)/s, 2);
20
+ return [value, index];
21
+ }
22
+ return [argv[index + 1], index + 1];
23
+ };
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const raw = argv[i] ?? "";
26
+ const key = raw.includes("=") ? raw.split("=", 1)[0] : raw;
27
+ switch (key) {
28
+ case "-p":
29
+ break;
30
+ case "--model": {
31
+ const [value, nextIndex] = takeValue(i, raw);
32
+ if (value) {
33
+ args.model = value;
34
+ i = nextIndex;
35
+ }
36
+ break;
37
+ }
38
+ case "--resume": {
39
+ const [value, nextIndex] = takeValue(i, raw);
40
+ if (value) {
41
+ args.resume = value;
42
+ i = nextIndex;
43
+ }
44
+ break;
45
+ }
46
+ case "--verbose":
47
+ args.verbose = true;
48
+ break;
49
+ case "--append-system-prompt": {
50
+ const [value, nextIndex] = takeValue(i, raw);
51
+ if (value !== void 0) {
52
+ args.appendSystemPrompt = value;
53
+ i = nextIndex;
54
+ }
55
+ break;
56
+ }
57
+ case "--debug-dir": {
58
+ const [value, nextIndex] = takeValue(i, raw);
59
+ if (value) {
60
+ args.debugDir = value;
61
+ i = nextIndex;
62
+ }
63
+ break;
64
+ }
65
+ case "--idle-timeout": {
66
+ const [value, nextIndex] = takeValue(i, raw);
67
+ const parsed = Number(value);
68
+ if (!Number.isFinite(parsed) || parsed <= 0) {
69
+ throw new Error("Invalid --idle-timeout value: must be a positive number");
70
+ }
71
+ args.idleTimeout = parsed;
72
+ i = nextIndex;
73
+ break;
74
+ }
75
+ case "--sandbox": {
76
+ const [value, nextIndex] = takeValue(i, raw);
77
+ if (value === "none" || value === "standard" || value === "strict") {
78
+ args.sandbox = value;
79
+ i = nextIndex;
80
+ } else {
81
+ throw new Error("Invalid --sandbox value: must be one of none, standard, strict");
82
+ }
83
+ break;
84
+ }
85
+ case "--self-test":
86
+ args.selfTest = true;
87
+ break;
88
+ case "--version":
89
+ args.version = true;
90
+ break;
91
+ case "--help":
92
+ args.help = true;
93
+ break;
94
+ default:
95
+ break;
96
+ }
97
+ }
98
+ return args;
99
+ }
100
+ function printHelp() {
101
+ process.stdout.write(`Gemini CLI shim
102
+
103
+ Usage:
104
+ gemini-cli-shim --model <model> [options]
105
+
106
+ Options:
107
+ -p Prompt via stdin (accepted, optional)
108
+ --model <model> Gemini model or alias (for example: gemini-2.5-flash, google/gemini-2.5-pro, flash, pro)
109
+ --resume <session_id> Resume an existing Gemini session UUID
110
+ --verbose Verbose stderr logging
111
+ --append-system-prompt <text> Extra system instructions appended to the internal shim prompt
112
+ --idle-timeout <seconds> Baseline idle timeout before work starts (default: 120)
113
+ --debug-dir <path> Write raw debug logs into this directory
114
+ --sandbox <level> none | standard | strict
115
+ --self-test Verify environment and print JSON
116
+ --version Print version
117
+ --help Print help
118
+ `);
119
+ }
120
+ async function readStdinTrimmed() {
121
+ const chunks = [];
122
+ for await (const chunk of process.stdin) {
123
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
124
+ }
125
+ return Buffer.concat(chunks).toString("utf8").trim();
126
+ }
127
+
128
+ // node_modules/@shims/common/src/tools.ts
129
+ var STANDARD_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "LS"];
130
+
131
+ // src/constants.ts
132
+ var SHIM_NAME = "gemini-cli-shim";
133
+ var SHIM_VERSION = "0.1.0";
134
+ var KNOWN_TOOLS = [...STANDARD_TOOLS];
135
+ var MAX_SILENT_SUCCESS_RETRIES = 1;
136
+ var MAX_INVALID_JSON_REPAIRS = 2;
137
+
138
+ // src/shim.ts
139
+ import { spawn } from "node:child_process";
140
+ import path4 from "node:path";
141
+ import process2 from "node:process";
142
+ import { createInterface } from "node:readline";
143
+
144
+ // node_modules/@shims/common/src/timeout.ts
145
+ var IdleTimeoutError = class extends Error {
146
+ timeoutMs;
147
+ constructor(timeoutMs) {
148
+ super(`Idle timeout: no events received for ${timeoutMs}ms`);
149
+ this.name = "IdleTimeoutError";
150
+ this.timeoutMs = timeoutMs;
151
+ }
152
+ };
153
+ var BusyStepTimeoutError = class extends Error {
154
+ timeoutMs;
155
+ constructor(timeoutMs) {
156
+ super(`Busy-step stall timeout: no observed activity for ${timeoutMs}ms`);
157
+ this.name = "BusyStepTimeoutError";
158
+ this.timeoutMs = timeoutMs;
159
+ }
160
+ };
161
+ async function* withAdaptiveTimeout(events, options) {
162
+ const iterator = events[Symbol.asyncIterator]();
163
+ let state = "idle";
164
+ const busyTimeoutMs = Math.max(
165
+ options.busyTimeoutMs ?? options.idleTimeoutMs,
166
+ options.idleTimeoutMs
167
+ );
168
+ const controller = {
169
+ markBusy() {
170
+ state = "busy";
171
+ },
172
+ markIdle() {
173
+ state = "idle";
174
+ },
175
+ get state() {
176
+ return state;
177
+ }
178
+ };
179
+ try {
180
+ while (true) {
181
+ const timeoutMs = state === "busy" ? busyTimeoutMs : options.idleTimeoutMs;
182
+ let timeoutId;
183
+ try {
184
+ const result = await Promise.race([
185
+ iterator.next(),
186
+ new Promise((_, reject) => {
187
+ timeoutId = setTimeout(() => {
188
+ reject(
189
+ state === "busy" ? new BusyStepTimeoutError(timeoutMs) : new IdleTimeoutError(timeoutMs)
190
+ );
191
+ }, timeoutMs);
192
+ })
193
+ ]);
194
+ if (result.done) break;
195
+ options.onEvent?.(result.value, controller);
196
+ yield result.value;
197
+ } finally {
198
+ clearTimeout(timeoutId);
199
+ }
200
+ }
201
+ } finally {
202
+ void iterator.return?.();
203
+ }
204
+ }
205
+
206
+ // src/debug.ts
207
+ import { appendFile as appendFile2, writeFile } from "node:fs/promises";
208
+ import path from "node:path";
209
+
210
+ // src/filesystem.ts
211
+ import { appendFile, access, mkdir, readFile } from "node:fs/promises";
212
+ import { constants } from "node:fs";
213
+ async function pathExists(target) {
214
+ try {
215
+ await access(target, constants.F_OK);
216
+ return true;
217
+ } catch {
218
+ return false;
219
+ }
220
+ }
221
+ async function ensureDir(dir) {
222
+ await mkdir(dir, { recursive: true });
223
+ }
224
+ async function writeJsonDebugLine(filePath, event) {
225
+ await appendFile(filePath, JSON.stringify(event) + "\n", "utf8");
226
+ }
227
+ async function sleep(ms) {
228
+ await new Promise((resolve) => setTimeout(resolve, ms));
229
+ }
230
+ async function findInvalidJsonFiles(filePaths) {
231
+ const invalid = [];
232
+ for (const filePath of filePaths) {
233
+ if (!filePath.toLowerCase().endsWith(".json")) {
234
+ continue;
235
+ }
236
+ try {
237
+ const content = await readFile(filePath, "utf8");
238
+ JSON.parse(content);
239
+ } catch (error) {
240
+ const message = error instanceof Error ? error.message : String(error);
241
+ invalid.push({ filePath, error: message });
242
+ }
243
+ }
244
+ return invalid;
245
+ }
246
+
247
+ // src/debug.ts
248
+ async function bindDebugSession(debug, sessionId) {
249
+ if (!debug.debugDir || debug.sessionId) return;
250
+ debug.sessionId = sessionId;
251
+ debug.jsonlPath = path.join(debug.debugDir, `session-${sessionId}.raw.jsonl`);
252
+ debug.logPath = path.join(debug.debugDir, `session-${sessionId}.raw.log`);
253
+ await ensureDir(debug.debugDir);
254
+ for (const event of debug.bufferedEvents) {
255
+ await writeJsonDebugLine(debug.jsonlPath, event);
256
+ }
257
+ debug.bufferedEvents = [];
258
+ if (debug.bufferedStderr.length > 0) {
259
+ await ensureLogFile(debug.logPath, debug.bufferedStderr.join(""));
260
+ debug.bufferedStderr = [];
261
+ } else {
262
+ await ensureLogFile(debug.logPath, "");
263
+ }
264
+ }
265
+ async function captureStderr(debug, text) {
266
+ if (!debug.debugDir) return;
267
+ if (debug.logPath) {
268
+ await ensureLogFile(debug.logPath, text, true);
269
+ } else {
270
+ debug.bufferedStderr.push(text);
271
+ }
272
+ }
273
+ async function writeUnknownLog(debug, text) {
274
+ if (!debug.debugDir) return;
275
+ const unknownLog = path.join(debug.debugDir, "session-unknown.raw.log");
276
+ await ensureLogFile(unknownLog, text, true);
277
+ }
278
+ async function ensureLogFile(filePath, text, appendOnly = false) {
279
+ if (!await pathExists(filePath)) {
280
+ await ensureDir(path.dirname(filePath));
281
+ await writeFile(filePath, text, "utf8");
282
+ return;
283
+ }
284
+ if (!appendOnly && text === "") {
285
+ return;
286
+ }
287
+ await appendFile2(filePath, text, "utf8");
288
+ }
289
+
290
+ // src/ids.ts
291
+ var NIL_UUID = "00000000-0000-0000-0000-000000000000";
292
+ var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
293
+ function validateResumeSessionId(value) {
294
+ if (!UUID_V4_REGEX.test(value)) {
295
+ throw new Error(`Invalid session ID: ${value}`);
296
+ }
297
+ }
298
+ function generateMessageId() {
299
+ return `msg_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
300
+ }
301
+ function generateToolUseId() {
302
+ return `toolu_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;
303
+ }
304
+
305
+ // src/models.ts
306
+ function formatGeminiOutputModel(model) {
307
+ const trimmed = model.trim();
308
+ if (!trimmed) {
309
+ return "google/gemini-2.5-flash";
310
+ }
311
+ return trimmed.includes("/") ? trimmed : `google/${trimmed}`;
312
+ }
313
+ function normalizeModelForGeminiCli(model) {
314
+ const trimmed = model.trim();
315
+ if (!trimmed) {
316
+ return {
317
+ requested: "gemini-2.5-flash",
318
+ outputModel: "google/gemini-2.5-flash",
319
+ geminiModel: "gemini-2.5-flash"
320
+ };
321
+ }
322
+ const short = trimmed.toLowerCase();
323
+ if (short === "flash") {
324
+ return {
325
+ requested: trimmed,
326
+ outputModel: "google/gemini-2.5-flash",
327
+ geminiModel: "gemini-2.5-flash"
328
+ };
329
+ }
330
+ if (short === "pro") {
331
+ return {
332
+ requested: trimmed,
333
+ outputModel: "google/gemini-2.5-pro",
334
+ geminiModel: "gemini-2.5-pro"
335
+ };
336
+ }
337
+ if (trimmed.includes("/")) {
338
+ const [, rest] = trimmed.split(/\/(.*)/s, 2);
339
+ const geminiModel = rest || trimmed;
340
+ return {
341
+ requested: trimmed,
342
+ outputModel: formatGeminiOutputModel(trimmed),
343
+ geminiModel
344
+ };
345
+ }
346
+ return {
347
+ requested: trimmed,
348
+ outputModel: formatGeminiOutputModel(trimmed),
349
+ geminiModel: trimmed
350
+ };
351
+ }
352
+ function getApiKeySource() {
353
+ if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_GENAI_USE_VERTEXAI === "true" || process.env.GOOGLE_GENAI_USE_GCA === "true") {
354
+ return "env";
355
+ }
356
+ return "none";
357
+ }
358
+
359
+ // src/output.ts
360
+ function syntheticErrorMessage(prefix, text, model = "<synthetic>") {
361
+ return {
362
+ type: "assistant",
363
+ message: {
364
+ id: NIL_UUID,
365
+ type: "message",
366
+ role: "assistant",
367
+ model,
368
+ content: [{ type: "text", text: `${prefix}: ${text}` }],
369
+ stop_reason: null
370
+ }
371
+ };
372
+ }
373
+ function emit(message) {
374
+ process.stdout.write(JSON.stringify(message) + "\n");
375
+ }
376
+ async function flushStdout() {
377
+ await new Promise((resolve, reject) => {
378
+ process.stdout.write("", (error) => {
379
+ if (error) reject(error);
380
+ else resolve();
381
+ });
382
+ });
383
+ }
384
+ function summarizeText(text) {
385
+ const trimmed = text.trim();
386
+ if (!trimmed) {
387
+ return "Completed successfully.";
388
+ }
389
+ return trimmed.length > 500 ? `${trimmed.slice(0, 497)}...` : trimmed;
390
+ }
391
+ function countNumberedSteps(text) {
392
+ return text.match(/(?:^|\n)\s*\d+\.\s+/g)?.length ?? 0;
393
+ }
394
+ function shouldRetrySilentSuccessTurn(input) {
395
+ return !input.isError && !input.sawAssistantText && !input.sawToolUse && !input.sawToolResult;
396
+ }
397
+
398
+ // src/process-utils.ts
399
+ import { readFile as readFile2 } from "node:fs/promises";
400
+ import os from "node:os";
401
+ import path2 from "node:path";
402
+ function trackChildExit(child) {
403
+ if (child.exitCode !== null) {
404
+ return Promise.resolve(child.exitCode);
405
+ }
406
+ return new Promise((resolve, reject) => {
407
+ const onError = (error) => {
408
+ cleanup();
409
+ reject(error);
410
+ };
411
+ const onClose = (code) => {
412
+ cleanup();
413
+ resolve(code ?? child.exitCode ?? 1);
414
+ };
415
+ const cleanup = () => {
416
+ child.removeListener("error", onError);
417
+ child.removeListener("close", onClose);
418
+ };
419
+ child.once("error", onError);
420
+ child.once("close", onClose);
421
+ });
422
+ }
423
+ async function which(command) {
424
+ const isWindows = process.platform === "win32";
425
+ const { spawn: spawn2 } = await import("node:child_process");
426
+ return await new Promise((resolve) => {
427
+ const proc = spawn2(isWindows ? "where" : "which", [command], {
428
+ stdio: ["ignore", "pipe", "ignore"],
429
+ shell: isWindows
430
+ });
431
+ let output = "";
432
+ proc.stdout.on("data", (chunk) => {
433
+ output += String(chunk);
434
+ });
435
+ proc.on("close", (code) => {
436
+ if (code === 0) {
437
+ resolve(output.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? null);
438
+ } else {
439
+ resolve(null);
440
+ }
441
+ });
442
+ });
443
+ }
444
+ async function loadGeminiSettingsAuthType() {
445
+ try {
446
+ const settingsPath = path2.join(os.homedir(), ".gemini", "settings.json");
447
+ const content = JSON.parse(await readFile2(settingsPath, "utf8"));
448
+ return content?.security?.auth?.selectedType ?? null;
449
+ } catch {
450
+ return null;
451
+ }
452
+ }
453
+
454
+ // src/prompts.ts
455
+ function buildGeminiPrompt(prompt, appendSystemPrompt) {
456
+ const internalInstructions = [
457
+ "You are running behind a machine-oriented shim.",
458
+ "Answer directly from the conversation context and tool results whenever possible.",
459
+ "If the user explicitly asks you to create, update, or maintain a file, do that with tools before ending the turn.",
460
+ "When the user gives a numbered or ordered task list, complete every requested step in sequence before finishing.",
461
+ "Do not stop after an intermediate answer if additional requested steps remain.",
462
+ "If the user asks for ongoing research notes or a progress file, keep that file updated as you work.",
463
+ "If the user asks for research, use the available search/web/file tools rather than answering from memory alone whenever the request calls for current sources.",
464
+ "When creating machine-readable files such as JSON, ensure the final file contents are syntactically valid before ending the turn.",
465
+ "Do not delegate to CLI help, documentation helpers, or other subagents unless the user explicitly asks about Gemini CLI usage or external documentation.",
466
+ "If the user asks what they told you earlier in this same conversation, answer from the conversation history directly."
467
+ ].join(" ");
468
+ if (!appendSystemPrompt?.trim()) {
469
+ return [
470
+ "SYSTEM INSTRUCTIONS (highest priority for this run):",
471
+ internalInstructions,
472
+ "",
473
+ "USER PROMPT:",
474
+ prompt
475
+ ].join("\n");
476
+ }
477
+ return [
478
+ "SYSTEM INSTRUCTIONS (highest priority for this run):",
479
+ internalInstructions,
480
+ "",
481
+ "ADDITIONAL CALLER SYSTEM INSTRUCTIONS:",
482
+ appendSystemPrompt.trim(),
483
+ "",
484
+ "USER PROMPT:",
485
+ prompt
486
+ ].join("\n");
487
+ }
488
+ function buildSilentTurnRecoveryPrompt() {
489
+ return [
490
+ "System: Your previous turn produced no assistant-visible text, tool calls, or tool results.",
491
+ "Continue the pending user request now.",
492
+ "You must either produce assistant text or use tools to complete the requested work before ending the turn.",
493
+ "Do not end with an empty response."
494
+ ].join(" ");
495
+ }
496
+ function buildRemainingStepsPrompt() {
497
+ return [
498
+ "System: Re-check the user's original numbered task list.",
499
+ "If any requested numbered steps are still incomplete, complete them now before you finish.",
500
+ "If everything is already complete, briefly confirm that all requested steps are done."
501
+ ].join(" ");
502
+ }
503
+ function buildInvalidJsonRepairPrompt(invalidFiles) {
504
+ return [
505
+ "System: Re-check the machine-readable files you created or modified.",
506
+ `These files are currently invalid JSON: ${invalidFiles.join("; ")}.`,
507
+ "Use tools to read the current file contents, repair them, and verify the final on-disk files parse as valid JSON before you end the turn.",
508
+ "If a previous repair introduced extra escaping, remove the extra escaping so the file itself is valid JSON.",
509
+ "After repairing them, briefly confirm which files were fixed."
510
+ ].join(" ");
511
+ }
512
+
513
+ // src/session-files.ts
514
+ import { readFile as readFile3, readdir } from "node:fs/promises";
515
+ import os2 from "node:os";
516
+ import path3 from "node:path";
517
+ var sessionFileCache = /* @__PURE__ */ new Map();
518
+ async function findGeminiSessionFile(sessionId) {
519
+ const cached = sessionFileCache.get(sessionId);
520
+ if (cached && await pathExists(cached)) {
521
+ return cached;
522
+ }
523
+ const baseDir = path3.join(os2.homedir(), ".gemini", "tmp");
524
+ if (!await pathExists(baseDir)) {
525
+ return void 0;
526
+ }
527
+ const prefix = sessionId.slice(0, 8);
528
+ const firstLevel = await readdir(baseDir, { withFileTypes: true });
529
+ for (const entry of firstLevel) {
530
+ if (!entry.isDirectory()) continue;
531
+ const chatsDir = path3.join(baseDir, entry.name, "chats");
532
+ if (!await pathExists(chatsDir)) continue;
533
+ const files = await readdir(chatsDir);
534
+ for (const file of files) {
535
+ if (!file.startsWith("session-") || !file.endsWith(".json") || !file.includes(prefix)) continue;
536
+ const filePath = path3.join(chatsDir, file);
537
+ try {
538
+ const content = JSON.parse(await readFile3(filePath, "utf8"));
539
+ if (content.sessionId === sessionId) {
540
+ sessionFileCache.set(sessionId, filePath);
541
+ return filePath;
542
+ }
543
+ } catch {
544
+ continue;
545
+ }
546
+ }
547
+ }
548
+ return void 0;
549
+ }
550
+ async function readSessionFile(sessionId) {
551
+ const filePath = await findGeminiSessionFile(sessionId);
552
+ if (!filePath) return void 0;
553
+ try {
554
+ return JSON.parse(await readFile3(filePath, "utf8"));
555
+ } catch {
556
+ return void 0;
557
+ }
558
+ }
559
+ async function waitForToolRecord(sessionId, toolId, timeoutMs = 2e3) {
560
+ const started = Date.now();
561
+ while (Date.now() - started <= timeoutMs) {
562
+ const data = await readSessionFile(sessionId);
563
+ if (data?.messages.some((message) => message.toolCalls?.some((call) => call.id === toolId))) {
564
+ return data;
565
+ }
566
+ await sleep(100);
567
+ }
568
+ return readSessionFile(sessionId);
569
+ }
570
+ function extractMeaningfulToolContent(session, toolId, fallbackOutput) {
571
+ if (fallbackOutput && fallbackOutput.trim()) {
572
+ return fallbackOutput;
573
+ }
574
+ const toolCall = session?.messages.flatMap((message) => message.toolCalls ?? []).find((call) => call.id === toolId);
575
+ if (!toolCall) {
576
+ return fallbackOutput && fallbackOutput.trim() ? fallbackOutput : void 0;
577
+ }
578
+ const functionResponseOutput = extractOutputFromResult(toolCall.result);
579
+ if (functionResponseOutput?.trim()) {
580
+ return functionResponseOutput;
581
+ }
582
+ const resultDisplay = toolCall.resultDisplay;
583
+ if (typeof resultDisplay === "string" && resultDisplay.trim()) {
584
+ return resultDisplay;
585
+ }
586
+ if (resultDisplay && typeof resultDisplay === "object") {
587
+ const record = resultDisplay;
588
+ if (typeof record.fileDiff === "string" && record.fileDiff.trim()) {
589
+ return record.fileDiff;
590
+ }
591
+ if (typeof record.filePath === "string") {
592
+ return `File updated: ${record.filePath}`;
593
+ }
594
+ if (typeof record.fileName === "string") {
595
+ return `File updated: ${record.fileName}`;
596
+ }
597
+ }
598
+ return fallbackOutput && fallbackOutput.trim() ? fallbackOutput : void 0;
599
+ }
600
+ function extractOutputFromResult(result) {
601
+ if (!Array.isArray(result)) return void 0;
602
+ for (const item of result) {
603
+ if (!item || typeof item !== "object") continue;
604
+ const functionResponse = item.functionResponse;
605
+ if (!functionResponse || typeof functionResponse !== "object") continue;
606
+ const response = functionResponse.response;
607
+ if (!response || typeof response !== "object") continue;
608
+ const output = response.output;
609
+ if (typeof output === "string") {
610
+ return output;
611
+ }
612
+ }
613
+ return void 0;
614
+ }
615
+ function aggregateInvocationUsage(session, invocationStartIso) {
616
+ if (!session) return void 0;
617
+ const startMs = Date.parse(invocationStartIso);
618
+ const totals = /* @__PURE__ */ new Map();
619
+ for (const message of session.messages) {
620
+ if (message.type !== "gemini" || !message.model || !message.tokens) continue;
621
+ if (Date.parse(message.timestamp) < startMs) continue;
622
+ const current = totals.get(message.model) ?? {
623
+ input_tokens: 0,
624
+ output_tokens: 0,
625
+ cache_read_input_tokens: 0,
626
+ cost_usd: 0
627
+ };
628
+ current.input_tokens += Number(message.tokens.input ?? 0);
629
+ current.output_tokens += Number(message.tokens.output ?? 0);
630
+ current.cache_read_input_tokens = (current.cache_read_input_tokens ?? 0) + Number(message.tokens.cached ?? 0);
631
+ totals.set(message.model, current);
632
+ }
633
+ if (totals.size <= 1) return void 0;
634
+ return Object.fromEntries(
635
+ [...totals.entries()].map(([model, usage]) => [formatGeminiOutputModel(model), usage])
636
+ );
637
+ }
638
+
639
+ // src/tools.ts
640
+ function camelToSnake(value) {
641
+ return value.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
642
+ }
643
+ function topLevelSnakeCase(input) {
644
+ if (!input) return void 0;
645
+ const output = {};
646
+ for (const [key, value] of Object.entries(input)) {
647
+ output[camelToSnake(key)] = value;
648
+ }
649
+ return output;
650
+ }
651
+ var TOOL_NAME_MAP = /* @__PURE__ */ new Map([
652
+ ["read_file", "Read"],
653
+ ["readfile", "Read"],
654
+ ["write_file", "Write"],
655
+ ["writefile", "Write"],
656
+ ["replace", "Edit"],
657
+ ["edit", "Edit"],
658
+ ["run_shell_command", "Bash"],
659
+ ["shell", "Bash"],
660
+ ["bash", "Bash"],
661
+ ["glob", "Glob"],
662
+ ["search_file_content", "Grep"],
663
+ ["grep", "Grep"],
664
+ ["list_directory", "LS"],
665
+ ["ls", "LS"]
666
+ ]);
667
+ function normalizeToolName(name) {
668
+ return TOOL_NAME_MAP.get(name.toLowerCase()) ?? name;
669
+ }
670
+ function normalizeToolInput(name, input) {
671
+ const snake = topLevelSnakeCase(input);
672
+ if (!snake) return void 0;
673
+ switch (name) {
674
+ case "Read": {
675
+ const filePath = snake.file_path ?? snake.path;
676
+ const result = {};
677
+ if (filePath !== void 0) result.file_path = filePath;
678
+ if (snake.offset !== void 0) result.offset = snake.offset;
679
+ if (snake.limit !== void 0) result.limit = snake.limit;
680
+ return result;
681
+ }
682
+ case "Write": {
683
+ const filePath = snake.file_path ?? snake.path;
684
+ const result = {};
685
+ if (filePath !== void 0) result.file_path = filePath;
686
+ if (snake.content !== void 0) result.content = snake.content;
687
+ return result;
688
+ }
689
+ case "Edit":
690
+ return snake;
691
+ case "Bash":
692
+ return {
693
+ command: snake.command,
694
+ ...snake.description !== void 0 ? { description: snake.description } : {},
695
+ ...snake.directory !== void 0 ? { directory: snake.directory } : {}
696
+ };
697
+ case "Glob": {
698
+ const out = {};
699
+ if (snake.pattern !== void 0) out.pattern = snake.pattern;
700
+ if (snake.path !== void 0) out.path = snake.path;
701
+ if (snake.case_sensitive !== void 0) out.case_sensitive = snake.case_sensitive;
702
+ if (snake.respect_git_ignore !== void 0) out.respect_git_ignore = snake.respect_git_ignore;
703
+ return out;
704
+ }
705
+ case "Grep": {
706
+ const out = {};
707
+ if (snake.pattern !== void 0) out.pattern = snake.pattern;
708
+ if (snake.path !== void 0) out.path = snake.path;
709
+ if (snake.include !== void 0) out.glob = snake.include;
710
+ return out;
711
+ }
712
+ case "LS": {
713
+ const out = {};
714
+ if (snake.path !== void 0) out.path = snake.path;
715
+ if (snake.ignore !== void 0) out.ignore = snake.ignore;
716
+ if (snake.respect_git_ignore !== void 0) out.respect_git_ignore = snake.respect_git_ignore;
717
+ return out;
718
+ }
719
+ default:
720
+ return snake;
721
+ }
722
+ }
723
+ function extractToolFilePath(input) {
724
+ const candidate = input?.file_path ?? input?.path;
725
+ return typeof candidate === "string" ? candidate : void 0;
726
+ }
727
+
728
+ // src/shim.ts
729
+ async function runShim(args, prompt) {
730
+ const invocationStart = (/* @__PURE__ */ new Date()).toISOString();
731
+ const startTime = Date.now();
732
+ const { outputModel, geminiModel } = normalizeModelForGeminiCli(args.model);
733
+ const cwd = process2.cwd();
734
+ const debug = {
735
+ debugDir: args.debugDir,
736
+ bufferedEvents: [],
737
+ bufferedStderr: []
738
+ };
739
+ if (debug.debugDir) {
740
+ await ensureDir(debug.debugDir);
741
+ }
742
+ if (args.resume) {
743
+ try {
744
+ validateResumeSessionId(args.resume);
745
+ } catch (error) {
746
+ const message = error instanceof Error ? error.message : String(error);
747
+ await writeUnknownLog(debug, `${message}
748
+ `);
749
+ throw error;
750
+ }
751
+ }
752
+ const geminiPath = await which("gemini");
753
+ if (!geminiPath) {
754
+ process2.stderr.write("Gemini CLI not found in PATH.\n");
755
+ await writeUnknownLog(debug, "Gemini CLI not found in PATH.\n");
756
+ return 1;
757
+ }
758
+ let interrupted = false;
759
+ let activeChild;
760
+ let systemEmitted = false;
761
+ let resultEmitted = false;
762
+ let sessionId = args.resume;
763
+ let actualModel = outputModel;
764
+ let durationApiMs = 0;
765
+ let usage;
766
+ let modelUsage;
767
+ let finalIsError = false;
768
+ let numTurns = 0;
769
+ let remainingSilentSuccessRetries = MAX_SILENT_SUCCESS_RETRIES;
770
+ let remainingInvalidJsonRepairs = MAX_INVALID_JSON_REPAIRS;
771
+ let completionCheckIssued = false;
772
+ let totalToolUseCount = 0;
773
+ let resumeSessionId = args.resume;
774
+ let attemptPrompt = buildGeminiPrompt(prompt, args.appendSystemPrompt);
775
+ const toolIdMap = /* @__PURE__ */ new Map();
776
+ const emittedToolUses = /* @__PURE__ */ new Set();
777
+ const emittedToolResults = /* @__PURE__ */ new Set();
778
+ const pendingToolResults = /* @__PURE__ */ new Map();
779
+ const assistantTextChunks = [];
780
+ let pendingAssistantText = "";
781
+ let currentAssistantStreamHasDelta = false;
782
+ let lastSyntheticError;
783
+ const candidateJsonFiles = /* @__PURE__ */ new Set();
784
+ const numberedStepCount = countNumberedSteps(prompt);
785
+ const flushAssistantText = () => {
786
+ if (!pendingAssistantText) return;
787
+ assistantTextChunks.push(pendingAssistantText);
788
+ const message = {
789
+ type: "assistant",
790
+ message: {
791
+ id: generateMessageId(),
792
+ type: "message",
793
+ role: "assistant",
794
+ model: actualModel,
795
+ content: [{ type: "text", text: pendingAssistantText }],
796
+ stop_reason: null
797
+ }
798
+ };
799
+ emit(message);
800
+ pendingAssistantText = "";
801
+ currentAssistantStreamHasDelta = false;
802
+ };
803
+ const emitFinalResult = async (isError, resultText) => {
804
+ const result = {
805
+ type: "result",
806
+ subtype: isError ? "error" : "success",
807
+ is_error: isError,
808
+ duration_ms: Date.now() - startTime,
809
+ duration_api_ms: durationApiMs,
810
+ num_turns: Math.max(1, numTurns),
811
+ result: resultText,
812
+ session_id: sessionId,
813
+ usage,
814
+ ...modelUsage ? { model_usage: modelUsage } : {}
815
+ };
816
+ emit(result);
817
+ resultEmitted = true;
818
+ finalIsError = isError;
819
+ await flushStdout();
820
+ return isError ? 1 : 0;
821
+ };
822
+ const maybeRepairInvalidJsonFiles = async () => {
823
+ const invalidJsonFiles = await findInvalidJsonFiles(candidateJsonFiles);
824
+ if (invalidJsonFiles.length === 0) {
825
+ return void 0;
826
+ }
827
+ const invalidFileDescriptions = invalidJsonFiles.map(({ filePath, error }) => {
828
+ const relativePath = path4.relative(cwd, filePath) || filePath;
829
+ return `${relativePath} (${error})`;
830
+ });
831
+ const errorText = `Gemini left invalid JSON files: ${invalidFileDescriptions.join("; ")}`;
832
+ if (remainingInvalidJsonRepairs > 0 && sessionId) {
833
+ remainingInvalidJsonRepairs -= 1;
834
+ resumeSessionId = sessionId;
835
+ attemptPrompt = buildInvalidJsonRepairPrompt(invalidFileDescriptions);
836
+ if (args.verbose) {
837
+ process2.stderr.write(
838
+ `[shim] Gemini left invalid JSON files for session ${sessionId}; requesting repair: ${invalidFileDescriptions.join(", ")}
839
+ `
840
+ );
841
+ }
842
+ return "continue";
843
+ }
844
+ emit(syntheticErrorMessage("Agent Error", errorText));
845
+ return await emitFinalResult(true, errorText);
846
+ };
847
+ const signalHandler = async () => {
848
+ if (interrupted) return;
849
+ interrupted = true;
850
+ activeChild?.kill("SIGTERM");
851
+ flushAssistantText();
852
+ if (systemEmitted && !resultEmitted) {
853
+ await emitFinalResult(false, summarizeText(assistantTextChunks.join("")));
854
+ }
855
+ process2.exit(0);
856
+ };
857
+ process2.once("SIGINT", signalHandler);
858
+ process2.once("SIGTERM", signalHandler);
859
+ try {
860
+ while (true) {
861
+ numTurns += 1;
862
+ lastSyntheticError = void 0;
863
+ let syntheticErrorEmitted = false;
864
+ const child = spawnGemini({
865
+ args,
866
+ cwd,
867
+ geminiModel,
868
+ geminiPath,
869
+ resumeSessionId
870
+ });
871
+ activeChild = child;
872
+ const childExitPromise = trackChildExit(child);
873
+ child.stdin.write(attemptPrompt);
874
+ child.stdin.end();
875
+ child.stderr.on("data", async (chunk) => {
876
+ const text = String(chunk);
877
+ if (args.verbose) {
878
+ process2.stderr.write(`[gemini-stderr] ${text}`);
879
+ }
880
+ await captureStderr(debug, text);
881
+ });
882
+ const eventIterator = readGeminiEvents(child, debug, args.verbose);
883
+ let attemptSawAssistantText = false;
884
+ let attemptSawToolUse = false;
885
+ let attemptSawToolResult = false;
886
+ let retryDueToSilentSuccess = false;
887
+ let continueWithFollowUpPrompt = false;
888
+ const emitToolResultFromEvent = async (event) => {
889
+ if (emittedToolResults.has(event.tool_id)) {
890
+ return;
891
+ }
892
+ const publicToolId = toolIdMap.get(event.tool_id);
893
+ if (!publicToolId) {
894
+ pendingToolResults.set(event.tool_id, event);
895
+ return;
896
+ }
897
+ const sessionData = sessionId ? await waitForToolRecord(sessionId, event.tool_id) : void 0;
898
+ const contentText = sessionId ? extractMeaningfulToolContent(sessionData, event.tool_id, event.output) : event.output;
899
+ const userMessage = {
900
+ type: "user",
901
+ message: {
902
+ role: "user",
903
+ content: [
904
+ event.status === "error" ? {
905
+ type: "tool_result",
906
+ tool_use_id: publicToolId,
907
+ content: {
908
+ is_error: true,
909
+ error: event.error?.message || contentText || "Tool execution failed"
910
+ }
911
+ } : {
912
+ type: "tool_result",
913
+ tool_use_id: publicToolId,
914
+ content: contentText || `Tool completed: ${event.tool_id}`
915
+ }
916
+ ]
917
+ }
918
+ };
919
+ emit(userMessage);
920
+ emittedToolResults.add(event.tool_id);
921
+ };
922
+ try {
923
+ attemptLoop: for await (const event of withAdaptiveTimeout(eventIterator, {
924
+ idleTimeoutMs: args.idleTimeout * 1e3,
925
+ busyTimeoutMs: Math.max(args.idleTimeout * 1e3, 3e5),
926
+ onEvent(event2, controller) {
927
+ if (event2.type === "tool_use" || event2.type === "message" && event2.role === "assistant" && event2.content.length > 0) {
928
+ controller.markBusy();
929
+ }
930
+ if (event2.type === "result") {
931
+ controller.markIdle();
932
+ }
933
+ }
934
+ })) {
935
+ switch (event.type) {
936
+ case "init": {
937
+ sessionId = event.session_id;
938
+ actualModel = formatGeminiOutputModel(event.model || geminiModel || outputModel);
939
+ await bindDebugSession(debug, sessionId);
940
+ if (!systemEmitted) {
941
+ const system = {
942
+ type: "system",
943
+ subtype: "init",
944
+ cwd,
945
+ session_id: sessionId,
946
+ tools: KNOWN_TOOLS,
947
+ model: actualModel,
948
+ permissionMode: "bypassPermissions",
949
+ apiKeySource: getApiKeySource(),
950
+ mcp_servers: []
951
+ };
952
+ emit(system);
953
+ systemEmitted = true;
954
+ }
955
+ break;
956
+ }
957
+ case "message": {
958
+ if (event.role !== "assistant") {
959
+ break;
960
+ }
961
+ if (!event.content) {
962
+ break;
963
+ }
964
+ if (event.delta) {
965
+ currentAssistantStreamHasDelta = true;
966
+ pendingAssistantText += event.content;
967
+ if (event.content.trim().length > 0) {
968
+ attemptSawAssistantText = true;
969
+ }
970
+ break;
971
+ }
972
+ if (currentAssistantStreamHasDelta) {
973
+ break;
974
+ }
975
+ pendingAssistantText += event.content;
976
+ if (event.content.trim().length > 0) {
977
+ attemptSawAssistantText = true;
978
+ }
979
+ break;
980
+ }
981
+ case "tool_use": {
982
+ attemptSawToolUse = true;
983
+ if (emittedToolUses.has(event.tool_id)) {
984
+ break;
985
+ }
986
+ totalToolUseCount += 1;
987
+ flushAssistantText();
988
+ const publicToolId = toolIdMap.get(event.tool_id) ?? generateToolUseId();
989
+ toolIdMap.set(event.tool_id, publicToolId);
990
+ emittedToolUses.add(event.tool_id);
991
+ const normalizedName = normalizeToolName(event.tool_name);
992
+ const normalizedInput = normalizeToolInput(normalizedName, event.parameters);
993
+ const candidateFilePath = extractToolFilePath(normalizedInput);
994
+ if (candidateFilePath && (normalizedName === "Write" || normalizedName === "Edit") && candidateFilePath.toLowerCase().endsWith(".json")) {
995
+ candidateJsonFiles.add(path4.resolve(cwd, candidateFilePath));
996
+ }
997
+ const toolUse = {
998
+ type: "assistant",
999
+ message: {
1000
+ id: generateMessageId(),
1001
+ type: "message",
1002
+ role: "assistant",
1003
+ model: actualModel,
1004
+ content: [
1005
+ {
1006
+ type: "tool_use",
1007
+ id: publicToolId,
1008
+ name: normalizedName,
1009
+ input: normalizedInput
1010
+ }
1011
+ ],
1012
+ stop_reason: "tool_use"
1013
+ }
1014
+ };
1015
+ emit(toolUse);
1016
+ const pendingResult = pendingToolResults.get(event.tool_id);
1017
+ if (pendingResult) {
1018
+ pendingToolResults.delete(event.tool_id);
1019
+ await emitToolResultFromEvent(pendingResult);
1020
+ }
1021
+ break;
1022
+ }
1023
+ case "tool_result": {
1024
+ attemptSawToolResult = true;
1025
+ flushAssistantText();
1026
+ await emitToolResultFromEvent(event);
1027
+ break;
1028
+ }
1029
+ case "error": {
1030
+ flushAssistantText();
1031
+ if (event.severity === "error") {
1032
+ lastSyntheticError = event.message;
1033
+ finalIsError = true;
1034
+ if (!syntheticErrorEmitted) {
1035
+ emit(syntheticErrorMessage("Agent Error", event.message));
1036
+ syntheticErrorEmitted = true;
1037
+ }
1038
+ }
1039
+ break;
1040
+ }
1041
+ case "result": {
1042
+ flushAssistantText();
1043
+ durationApiMs = Number(event.stats?.duration_ms ?? Date.now() - startTime);
1044
+ usage = {
1045
+ input_tokens: event.stats?.input_tokens,
1046
+ output_tokens: event.stats?.output_tokens,
1047
+ cache_read_input_tokens: event.stats?.cached
1048
+ };
1049
+ const sessionData = sessionId ? await waitForSessionFile(sessionId) : void 0;
1050
+ modelUsage = aggregateInvocationUsage(sessionData, invocationStart);
1051
+ const isError = event.status === "error" || Boolean(lastSyntheticError) || Boolean(event.error?.message);
1052
+ if (isError && !syntheticErrorEmitted) {
1053
+ emit(
1054
+ syntheticErrorMessage(
1055
+ "Agent Error",
1056
+ event.error?.message || lastSyntheticError || "Gemini CLI reported an error"
1057
+ )
1058
+ );
1059
+ syntheticErrorEmitted = true;
1060
+ }
1061
+ const silentSuccess = shouldRetrySilentSuccessTurn({
1062
+ isError,
1063
+ sawAssistantText: attemptSawAssistantText,
1064
+ sawToolUse: attemptSawToolUse,
1065
+ sawToolResult: attemptSawToolResult
1066
+ });
1067
+ if (silentSuccess) {
1068
+ if (remainingSilentSuccessRetries > 0 && sessionId) {
1069
+ remainingSilentSuccessRetries -= 1;
1070
+ retryDueToSilentSuccess = true;
1071
+ continueWithFollowUpPrompt = true;
1072
+ resumeSessionId = sessionId;
1073
+ attemptPrompt = buildSilentTurnRecoveryPrompt();
1074
+ if (args.verbose) {
1075
+ process2.stderr.write(
1076
+ `[shim] Gemini returned a silent success turn for session ${sessionId}; retrying with continuation prompt.
1077
+ `
1078
+ );
1079
+ }
1080
+ break attemptLoop;
1081
+ }
1082
+ const message = "Gemini completed with no assistant text or tool activity.";
1083
+ emit(syntheticErrorMessage("Agent Error", message));
1084
+ const emittedCode2 = await emitFinalResult(true, message);
1085
+ await childExitPromise;
1086
+ activeChild = void 0;
1087
+ return emittedCode2;
1088
+ }
1089
+ const needsCompletionCheck = !isError && !completionCheckIssued && sessionId && numberedStepCount >= 2 && totalToolUseCount > 0 && totalToolUseCount < numberedStepCount;
1090
+ if (needsCompletionCheck) {
1091
+ completionCheckIssued = true;
1092
+ continueWithFollowUpPrompt = true;
1093
+ resumeSessionId = sessionId;
1094
+ attemptPrompt = buildRemainingStepsPrompt();
1095
+ if (args.verbose) {
1096
+ process2.stderr.write(
1097
+ `[shim] Gemini may have stopped before finishing a numbered task list for session ${sessionId}; requesting completion check.
1098
+ `
1099
+ );
1100
+ }
1101
+ break attemptLoop;
1102
+ }
1103
+ if (!isError) {
1104
+ const jsonRepairOutcome = await maybeRepairInvalidJsonFiles();
1105
+ if (jsonRepairOutcome === "continue") {
1106
+ continueWithFollowUpPrompt = true;
1107
+ break attemptLoop;
1108
+ }
1109
+ if (typeof jsonRepairOutcome === "number") {
1110
+ await childExitPromise;
1111
+ activeChild = void 0;
1112
+ return jsonRepairOutcome;
1113
+ }
1114
+ }
1115
+ const resultText = isError ? event.error?.message || lastSyntheticError || "Gemini CLI reported an error" : summarizeText(assistantTextChunks.join(""));
1116
+ const emittedCode = await emitFinalResult(isError, resultText);
1117
+ await childExitPromise;
1118
+ activeChild = void 0;
1119
+ return emittedCode;
1120
+ }
1121
+ }
1122
+ }
1123
+ flushAssistantText();
1124
+ const exitCode = await childExitPromise;
1125
+ activeChild = void 0;
1126
+ if (retryDueToSilentSuccess || continueWithFollowUpPrompt) {
1127
+ continue;
1128
+ }
1129
+ if (!resultEmitted && systemEmitted) {
1130
+ const sessionData = sessionId ? await waitForSessionFile(sessionId) : void 0;
1131
+ modelUsage = aggregateInvocationUsage(sessionData, invocationStart);
1132
+ const success = exitCode === 0 && !lastSyntheticError;
1133
+ const silentSuccess = shouldRetrySilentSuccessTurn({
1134
+ isError: !success,
1135
+ sawAssistantText: attemptSawAssistantText,
1136
+ sawToolUse: attemptSawToolUse,
1137
+ sawToolResult: attemptSawToolResult
1138
+ });
1139
+ if (silentSuccess) {
1140
+ if (remainingSilentSuccessRetries > 0 && sessionId) {
1141
+ remainingSilentSuccessRetries -= 1;
1142
+ resumeSessionId = sessionId;
1143
+ attemptPrompt = buildSilentTurnRecoveryPrompt();
1144
+ if (args.verbose) {
1145
+ process2.stderr.write(
1146
+ `[shim] Gemini exited after a silent success turn for session ${sessionId}; retrying with continuation prompt.
1147
+ `
1148
+ );
1149
+ }
1150
+ continue;
1151
+ }
1152
+ const message = "Gemini completed with no assistant text or tool activity.";
1153
+ emit(syntheticErrorMessage("Agent Error", message));
1154
+ return await emitFinalResult(true, message);
1155
+ }
1156
+ const needsCompletionCheck = success && !completionCheckIssued && sessionId && numberedStepCount >= 2 && totalToolUseCount > 0 && totalToolUseCount < numberedStepCount;
1157
+ if (needsCompletionCheck) {
1158
+ completionCheckIssued = true;
1159
+ resumeSessionId = sessionId;
1160
+ attemptPrompt = buildRemainingStepsPrompt();
1161
+ if (args.verbose) {
1162
+ process2.stderr.write(
1163
+ `[shim] Gemini may have exited before finishing a numbered task list for session ${sessionId}; requesting completion check.
1164
+ `
1165
+ );
1166
+ }
1167
+ continue;
1168
+ }
1169
+ if (success) {
1170
+ const jsonRepairOutcome = await maybeRepairInvalidJsonFiles();
1171
+ if (jsonRepairOutcome === "continue") {
1172
+ continue;
1173
+ }
1174
+ if (typeof jsonRepairOutcome === "number") {
1175
+ return jsonRepairOutcome;
1176
+ }
1177
+ } else if (!syntheticErrorEmitted) {
1178
+ emit(syntheticErrorMessage("Agent Error", lastSyntheticError || `Gemini CLI exited with code ${exitCode}`));
1179
+ syntheticErrorEmitted = true;
1180
+ }
1181
+ const resultText = success ? summarizeText(assistantTextChunks.join("")) : lastSyntheticError || `Gemini CLI exited with code ${exitCode}`;
1182
+ return await emitFinalResult(!success, resultText);
1183
+ }
1184
+ await flushStdout();
1185
+ return finalIsError ? 1 : exitCode === 0 ? 0 : 1;
1186
+ } catch (error) {
1187
+ const message = error instanceof Error ? error.message : String(error);
1188
+ const _isTimeout = error instanceof IdleTimeoutError || error instanceof BusyStepTimeoutError;
1189
+ if (args.verbose) {
1190
+ process2.stderr.write(`[shim] ${message}
1191
+ `);
1192
+ }
1193
+ if (debug.debugDir && !sessionId) {
1194
+ await writeUnknownLog(debug, `${message}
1195
+ `);
1196
+ }
1197
+ activeChild?.kill("SIGKILL");
1198
+ activeChild = void 0;
1199
+ flushAssistantText();
1200
+ if (systemEmitted) {
1201
+ emit(syntheticErrorMessage("Agent Error", message));
1202
+ return await emitFinalResult(true, message);
1203
+ }
1204
+ process2.stderr.write(`${message}
1205
+ `);
1206
+ await flushStdout();
1207
+ return 1;
1208
+ }
1209
+ }
1210
+ } finally {
1211
+ process2.removeListener("SIGINT", signalHandler);
1212
+ process2.removeListener("SIGTERM", signalHandler);
1213
+ }
1214
+ }
1215
+ function spawnGemini({
1216
+ args,
1217
+ cwd,
1218
+ geminiModel,
1219
+ geminiPath,
1220
+ resumeSessionId
1221
+ }) {
1222
+ const isWindows = process2.platform === "win32";
1223
+ const geminiArgs = ["--model", geminiModel, "--output-format", "stream-json", "--approval-mode", "yolo"];
1224
+ if (resumeSessionId) {
1225
+ geminiArgs.push("--resume", resumeSessionId);
1226
+ }
1227
+ if (args.sandbox === "standard" || args.sandbox === "strict") {
1228
+ geminiArgs.push("--sandbox");
1229
+ }
1230
+ const env = {
1231
+ ...process2.env,
1232
+ FORCE_COLOR: "0",
1233
+ GEMINI_SANDBOX: args.sandbox === "none" ? "false" : process2.env.GEMINI_SANDBOX || "true",
1234
+ BASH_ENV: ""
1235
+ };
1236
+ if (args.sandbox === "strict" && process2.platform === "darwin") {
1237
+ env.SEATBELT_PROFILE = "restrictive-open";
1238
+ }
1239
+ return spawn(geminiPath, geminiArgs, {
1240
+ cwd,
1241
+ stdio: ["pipe", "pipe", "pipe"],
1242
+ env,
1243
+ shell: isWindows
1244
+ });
1245
+ }
1246
+ async function* readGeminiEvents(child, debug, verbose) {
1247
+ const rl = createInterface({ input: child.stdout, crlfDelay: Infinity });
1248
+ for await (const line of rl) {
1249
+ if (!line.trim()) continue;
1250
+ let parsed;
1251
+ try {
1252
+ parsed = JSON.parse(line);
1253
+ } catch {
1254
+ if (verbose) {
1255
+ process2.stderr.write(`[shim] Skipping non-JSON Gemini stdout line: ${line}
1256
+ `);
1257
+ }
1258
+ continue;
1259
+ }
1260
+ if (debug.sessionId && debug.jsonlPath) {
1261
+ await writeJsonDebugLine(debug.jsonlPath, parsed);
1262
+ } else {
1263
+ debug.bufferedEvents.push(parsed);
1264
+ }
1265
+ yield parsed;
1266
+ }
1267
+ }
1268
+ async function waitForSessionFile(sessionId) {
1269
+ const started = Date.now();
1270
+ while (Date.now() - started <= 2e3) {
1271
+ const filePath = await findGeminiSessionFile(sessionId);
1272
+ if (filePath) {
1273
+ return await readSessionFile(sessionId);
1274
+ }
1275
+ await sleep(100);
1276
+ }
1277
+ return await readSessionFile(sessionId);
1278
+ }
1279
+ async function runSelfTest() {
1280
+ const geminiPath = await which("gemini");
1281
+ const authType = await loadGeminiSettingsAuthType();
1282
+ const checks = [
1283
+ {
1284
+ name: "agent_found",
1285
+ passed: Boolean(geminiPath),
1286
+ message: geminiPath ? `Found Gemini CLI at ${geminiPath}` : "Gemini CLI not found in PATH"
1287
+ },
1288
+ {
1289
+ name: "auth_configured",
1290
+ passed: Boolean(process2.env.GEMINI_API_KEY || process2.env.GOOGLE_API_KEY || authType),
1291
+ message: process2.env.GEMINI_API_KEY || process2.env.GOOGLE_API_KEY || authType ? `Authentication appears configured (${authType || "environment"})` : "No Gemini auth configuration detected"
1292
+ }
1293
+ ];
1294
+ const overallPassed = checks.every((check) => check.passed);
1295
+ const payload = {
1296
+ shim: { name: SHIM_NAME, version: SHIM_VERSION },
1297
+ agent: { name: "gemini", version: await detectGeminiVersion(), found: Boolean(geminiPath) },
1298
+ checks,
1299
+ overall: {
1300
+ passed: overallPassed,
1301
+ message: overallPassed ? "All checks passed" : "One or more checks failed"
1302
+ }
1303
+ };
1304
+ process2.stdout.write(JSON.stringify(payload, null, 2) + "\n");
1305
+ return overallPassed ? 0 : 1;
1306
+ }
1307
+ async function detectGeminiVersion() {
1308
+ const geminiPath = await which("gemini");
1309
+ if (!geminiPath) return "unknown";
1310
+ return await new Promise((resolve) => {
1311
+ const isWindows = process2.platform === "win32";
1312
+ const child = spawn(geminiPath, ["--version"], {
1313
+ stdio: ["ignore", "pipe", "ignore"],
1314
+ shell: isWindows
1315
+ });
1316
+ let output = "";
1317
+ child.stdout.on("data", (chunk) => {
1318
+ output += String(chunk);
1319
+ });
1320
+ child.on("close", () => resolve(output.trim() || "unknown"));
1321
+ child.on("error", () => resolve("unknown"));
1322
+ });
1323
+ }
1324
+
1325
+ // src/index.ts
1326
+ async function main() {
1327
+ try {
1328
+ const args = parseArgs(process3.argv.slice(2));
1329
+ if (args.help) {
1330
+ printHelp();
1331
+ return 0;
1332
+ }
1333
+ if (args.version) {
1334
+ process3.stdout.write(`${SHIM_VERSION}
1335
+ `);
1336
+ return 0;
1337
+ }
1338
+ if (args.selfTest) {
1339
+ return await runSelfTest();
1340
+ }
1341
+ const prompt = await readStdinTrimmed();
1342
+ if (!prompt) {
1343
+ return 0;
1344
+ }
1345
+ return await runShim(args, prompt);
1346
+ } catch (error) {
1347
+ const message = error instanceof Error ? error.message : String(error);
1348
+ process3.stderr.write(`${message}
1349
+ `);
1350
+ return 1;
1351
+ }
1352
+ }
1353
+ main().then((code) => {
1354
+ process3.exit(code);
1355
+ }).catch((error) => {
1356
+ const message = error instanceof Error ? error.message : String(error);
1357
+ process3.stderr.write(`${message}
1358
+ `);
1359
+ process3.exit(1);
1360
+ });