hankweave 0.3.3 → 0.5.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.
- package/README.md +76 -16
- package/dist/index.js +272 -266
- package/dist/index.js.map +32 -32
- package/dist/shims/codex/index.js +99 -71
- package/dist/shims/gemini/index.js +6 -5
- package/package.json +4 -7
- package/schemas/hank.schema.json +18 -0
- package/schemas/hankweave.schema.json +6 -0
- package/shims/codex/index.js +99 -71
- package/shims/gemini/index.js +6 -5
package/shims/gemini/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFileSync as
|
|
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=
|
|
2
|
+
import{readFileSync as de}from"fs";import{dirname as pe,join as ge}from"path";import{fileURLToPath as fe}from"url";function Y(s,e){let t={model:"",verbose:!1,idleTimeout:120,selfTest:!1,version:!1,help:!1};for(let i=0;i<s.length;i++){let r=s[i];if(r.includes("=")){let[a,l]=r.split("=",2);s.splice(i,1,a,l),r=a}switch(e&&r in e&&(r=e[r]),r){case"--model":t.model=s[++i];break;case"--resume":t.resume=s[++i];break;case"--verbose":t.verbose=!0;break;case"--append-system-prompt":t.appendSystemPrompt=s[++i];break;case"--debug-dir":t.debugDir=s[++i];break;case"--idle-timeout":{let a=Number(s[++i]);(!Number.isFinite(a)||a<=0)&&(console.error("Invalid --idle-timeout value: must be a positive number"),process.exit(1)),t.idleTimeout=a;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 J}from"fs/promises";import{isAbsolute as G,resolve as h}from"path";import{createInterface as ae}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",r=A(t?"where":"which",["gemini"],{shell:t}),a="";r.stdout?.on("data",l=>{a+=l.toString()}),r.on("close",l=>{if(l===0&&a.trim()){let c=A("gemini",["--version"],{shell:t}),o="";c.stdout?.on("data",m=>{o+=m.toString()}),c.on("close",()=>{e({found:!0,path:a.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 J(t,{recursive:!0});let i=h(t,`session-${e}.raw.jsonl`);this.rawEventStream=O(i,{flags:"a",encoding:"utf8"});let r=h(t,`session-${e}.raw.log`);this.stderrStream=O(r,{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:i}=await import("fs/promises"),r=h(t,`session-${this.currentSessionId}.raw.jsonl`),a=h(t,`session-${e}.raw.jsonl`),l=h(t,`session-${this.currentSessionId}.raw.log`),c=h(t,`session-${e}.raw.log`);try{await i(r,a)}catch(o){this.verbose&&console.error(`[gemini-cli-shim] Could not rename ${r}:`,o)}try{await i(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 J(t,{recursive:!0});let i=h(t,`session-${this.currentSessionId}.raw.log`),r=O(i,{flags:"a",encoding:"utf8"});await new Promise((a,l)=>{r.write(this.stderrBuffer,c=>{c?l(c):r.end(o=>{o?l(o):a()})})}),this.stderrBuffer=""}catch(t){console.error("[gemini-cli-shim] Error flushing buffered stderr:",t)}this.rawEventStream&&(e.push(new Promise((t,i)=>{this.rawEventStream?.end(r=>{r?i(r):t()})})),this.rawEventStream=null),this.stderrStream&&(e.push(new Promise((t,i)=>{this.stderrStream?.end(r=>{r?i(r):t()})})),this.stderrStream=null),await Promise.all(e)}async spawn(e){let t=["--model",this.options.model,"--output-format","stream-json","--yolo"];this.options.resume&&t.push("--resume",this.options.resume),this.verbose&&console.error("[gemini-cli-shim] Spawning gemini:",t.join(" "));let i=process.platform==="win32";this.process=A("gemini",t,{cwd:this.options.cwd,stdio:["pipe","pipe","pipe"],env:{...process.env},shell:i}),this.options.debugDir&&await this.initializeDebugStreams("unknown");let r=this.options.appendSystemPrompt?`${e}
|
|
4
4
|
|
|
5
|
-
Additional instructions: ${this.options.appendSystemPrompt}`:e;return this.process.stdin?.write(
|
|
6
|
-
${e.appendSystemPrompt}`:
|
|
5
|
+
Additional instructions: ${this.options.appendSystemPrompt}`:e;return this.process.stdin?.write(r),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,i=!1,r="",a=null;this.process.stderr.on("data",c=>{let o=c.toString();e+=o,this.writeStderr(o),!i&&(o.includes("Error resuming session:")||o.includes("Invalid session identifier"))&&(t=!0,i=!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=ae({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&&(r=m.session_id,this.options.debugDir&&this.currentSessionId==="unknown"&&await this.renameDebugStreams(r)),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),a=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 me}from"fs";import{resolve as ue}from"path";import{randomUUID as Ge}from"crypto";import Ne from"fs";import Ce from"path";var R=class extends Error{timeoutMs;constructor(e){super(`Idle timeout: no events received for ${e}ms`),this.name="IdleTimeoutError",this.timeoutMs=e}};async function*z(s,e){let t=s[Symbol.asyncIterator]();try{for(;;){let i;try{let r=await Promise.race([t.next(),new Promise((a,l)=>{i=setTimeout(()=>{l(new R(e))},e)})]);if(r.done)break;yield r.value}finally{clearTimeout(i)}}}finally{t.return?.()}}var q=["Read","Write","Edit","Bash","Glob","Grep","LS"];var H={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 V(s){if(!s){let e=process.env.MODEL;e?s=e:s="flash"}if(H[s]){let e=H[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 Z}from"crypto";function N(){let s=Date.now().toString(36),e=Z(5).toString("hex");return`msg_${s}${e}`}function $(){let s=Date.now().toString(36),e=Z(6).toString("hex");return`toolu_${s}${e}`}var x="00000000-0000-0000-0000-000000000000";var le={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 le[s]||s}function ce(s){return s.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`)}function X(s){if(!s)return s;let e={};for(let[t,i]of Object.entries(s))e[ce(t)]=i;return e}function k(){return[...q]}function y(s){console.log(JSON.stringify(s))}function C(){return process.env.GOOGLE_API_KEY?"GOOGLE_API_KEY":process.env.GEMINI_API_KEY?"GEMINI_API_KEY":"none"}async function ee(s,e){let t=Date.now(),i=0,r=0,a="",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.",re=e.appendSystemPrompt?`${I}
|
|
6
|
+
${e.appendSystemPrompt}`:I,ie={...e,appendSystemPrompt:re},L=new Set,U=new Map,B=new Map,w=[],T=N(),D=!1,v="";try{let P=await new _(ie).spawn(s),K=z(P,e.idleTimeout*1e3);for await(let n of K)switch(n.type){case"init":{a=n.session_id,d=!0;let u={type:"system",subtype:"init",cwd:e.cwd,session_id:a,tools:k(),model:E(e.model),permissionMode:"bypassPermissions",apiKeySource:C(),mcp_servers:[]};y(u),i=Date.now();break}case"message":{if(n.role==="assistant"){if(n.delta)D=!0,v+=n.content,w.push({type:"text",text:n.content});else if(!D)w.push({type:"text",text:n.content}),v=n.content;else if(n.content.length>v.length&&n.content.startsWith(v)){let u=n.content.slice(v.length);u.trim()&&(w.push({type:"text",text:u}),v=n.content)}}break}case"tool_use":{if(!L.has(n.tool_id)){L.add(n.tool_id);let u=$();U.set(n.tool_id,u);let S=Q(n.tool_name),b=X(n.parameters);B.set(n.tool_id,{name:S,params:n.parameters}),w.push({type:"tool_use",id:u,name:S,input:b});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=N(),D=!1,v="",l++}break}case"tool_result":{let u=U.get(n.tool_id)||$(),S=B.get(n.tool_id),b="";if(n.status==="error")b={is_error:!0,error:n.error||"Unknown error"};else{let F=n;if(b=n.output!==void 0?n.output:F.result||F.content||"",S?.name==="Read"&&b===""){let M=S.params?.file_path||S.params?.filePath||S.params?.path;if(M&&typeof M=="string")try{let W=ue(e.cwd,M);b=me(W,"utf-8")}catch{}}}y({type:"user",message:{role:"user",content:[{type:"tool_result",tool_use_id:u,content:b}]}}),D=!1,v="";break}case"result":{if(r=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)+(n.stats.input_tokens||0),c.output_tokens=(c.output_tokens||0)+(n.stats.output_tokens||0),o=n.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 n={type:"system",subtype:"init",cwd:e.cwd,session_id:a||x,tools:k(),model:E(e.model),permissionMode:"bypassPermissions",apiKeySource:C(),mcp_servers:[]};y(n),d=!0}y({type:"assistant",message:{id:x,type:"message",role:"assistant",model:"<synthetic>",content:[{type:"text",text:m}]}})}let oe=Date.now(),ne={type:"result",subtype:o?"error":"success",is_error:o,duration_ms:oe-t,duration_api_ms:r>0?r-i:0,num_turns:l,result:m,session_id:a,usage:c};return y(ne),await new Promise(p=>{process.stdout.write("",()=>p())}),{exitCode:o?1:0,sessionId:a}}async function se(s,e){let t={type:"system",subtype:"init",cwd:process.cwd(),session_id:e||x,tools:k(),model:"<unknown>",permissionMode:"bypassPermissions",apiKeySource:C(),mcp_servers:[]};y(t);let i={type:"assistant",message:{id:x,type:"message",role:"assistant",model:"<synthetic>",content:[{type:"text",text:`API Error: ${s}`}]}};y(i),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(a=>{process.stdout.write("",()=>a())})}var he=fe(import.meta.url),ye=pe(he);async function we(){let s=[];for await(let e of process.stdin)s.push(Buffer.from(e));return Buffer.concat(s).toString("utf-8").trim()}function te(){console.error(`
|
|
7
7
|
Gemini CLI Shim - Translate Gemini CLI to standardized JSONL output
|
|
8
8
|
|
|
9
9
|
Usage: gemini-cli-shim [options]
|
|
@@ -16,6 +16,7 @@ Optional Arguments:
|
|
|
16
16
|
--resume <session_id> Session ID to continue
|
|
17
17
|
--verbose Enable verbose logging to stderr
|
|
18
18
|
--append-system-prompt <txt> Additional system prompt to append
|
|
19
|
+
--idle-timeout <seconds> Max seconds between agent events before aborting (default: 120)
|
|
19
20
|
--debug-dir <path> Directory for debug logs and session data
|
|
20
21
|
--self-test Run environment verification
|
|
21
22
|
--version Print version and exit
|
|
@@ -26,4 +27,4 @@ Examples:
|
|
|
26
27
|
echo "Continue" | gemini-cli-shim --model pro --resume <session_id>
|
|
27
28
|
echo "Debug" | gemini-cli-shim --model flash --debug-dir ./debug
|
|
28
29
|
gemini-cli-shim --self-test
|
|
29
|
-
`)}function
|
|
30
|
+
`)}function ve(){try{let s=ge(ye,"..","package.json"),e=JSON.parse(de(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 i=s.every(a=>a.passed),r={shim:{name:"gemini-cli-shim",version:"1.0.0"},agent:{name:"gemini-cli",version:e.version||"unknown",found:e.found},checks:s,overall:{passed:i,message:i?"All checks passed":"Some checks failed"}};console.log(JSON.stringify(r,null,2)),process.exit(i?0:1)}async function be(){let s=Y(process.argv.slice(2),{"-m":"--model","-r":"--resume","-v":"--verbose","-h":"--help"});if(s.help&&(te(),process.exit(0)),s.version&&(ve(),process.exit(0)),s.selfTest){await Se();return}s.model||(console.error("Error: --model is required"),te(),process.exit(1));let e=await we();e||process.exit(0);let t=V(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 r=!1,a=()=>{r||(r=!0,s.verbose&&console.error("[gemini-cli-shim] Received interrupt signal, exiting gracefully"),process.exit(0))};process.on("SIGINT",a),process.on("SIGTERM",a);try{let l=await ee(e,{model:t,resume:s.resume,verbose:s.verbose,cwd:process.cwd(),appendSystemPrompt:s.appendSystemPrompt,debugDir:s.debugDir,idleTimeout:s.idleTimeout});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 se(c),process.exit(1)}}be().catch(s=>{console.error("Fatal error:",s),process.exit(1)});
|