brass-runtime 1.12.1 → 1.13.3

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.
@@ -1,16 +1,2022 @@
1
1
  #!/usr/bin/env node
2
- import {ta,pa,ha,ga,ba,T as T$1,V,U,ma,ia,oa,na,la,ka,sa,ra,qa}from'../../chunk-P4IND5C3.js';import {M as M$1,j as j$1,u,t}from'../../chunk-63MXGA7P.js';import {existsSync,readFileSync,readdirSync}from'fs';import {access,constants,mkdir,writeFile}from'fs/promises';import {resolve,join,isAbsolute,dirname}from'path';import {spawnSync}from'child_process';var Xe=new Function("specifier","return import(specifier)"),Ze=e=>["y","yes","approve","approved","si","s\xED","s"].includes(e),en=e=>["n","no","reject","rejected","deny","denied"].includes(e),nn=(e,n)=>{let t=e.trim().toLowerCase();return t?Ze(t)?{type:"approved"}:en(t)?{type:"rejected",reason:"Rejected by user."}:{type:"rejected",reason:`Unrecognized approval answer: ${e}`}:n.defaultAnswer==="approve"?{type:"approved"}:{type:"rejected",reason:"Rejected by default answer."}},Pe=(e={})=>({request:n=>j$1((t$1,o)=>{let r=false,a;return Xe("node:readline/promises").then(({createInterface:l})=>{if(r)return;let s=e.input??process.stdin,c=e.output??process.stderr??process.stdout,h=n.defaultAnswer==="approve"?"Y/n":"y/N";return a=l({input:s,output:c}),c?.write?.(`
3
- Approval required (${n.risk})
4
- `),c?.write?.(`Action: ${T$1(n.action)}
5
- `),c?.write?.(`Reason: ${n.reason}
6
- `),a.question(`Approve? [${h}] `)}).then(l=>{l===void 0||r||(r=true,a?.close?.(),o(u.succeed(nn(l,n))));}).catch(l=>{r||(r=true,a?.close?.(),o(u.failCause(t.fail({_tag:"AgentLoopError",message:`Approval prompt failed: ${String(l)}`}))));}),()=>{r||(r=true,a?.close?.());}})});var _=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),N=e=>{try{return JSON.parse(readFileSync(e,"utf8"))}catch{return}},Le=(e,n)=>{let t=resolve(e);for(;;){let o=join(t,"package.json"),r=N(o);if(_(r)&&r.name===n)return t;let a=resolve(t,"..");if(a===t)return;t=a;}},M=(e,n=["--version"],t)=>{let o=spawnSync(e,[...n],{cwd:t,encoding:"utf8",shell:false,timeout:1e4});if(!(o.error||(o.status??0)!==0))return `${o.stdout??""}${o.stderr??""}`.trim().split(/\r?\n/g)[0]},L=e=>!!(e&&process.env[e]),ln=()=>process.env.BRASS_AGENT_VSCODE_EXTENSION==="1",cn=e=>{let n=N(join(e,"package.json")),t=_(n)&&typeof n.packageManager=="string"?n.packageManager.split("@")[0]:void 0;return t?{manager:t,source:"package.json packageManager"}:existsSync(join(e,"pnpm-lock.yaml"))?{manager:"pnpm",source:"pnpm-lock.yaml"}:existsSync(join(e,"yarn.lock"))?{manager:"yarn",source:"yarn.lock"}:existsSync(join(e,"bun.lockb"))||existsSync(join(e,"bun.lock"))?{manager:"bun",source:"bun lockfile"}:existsSync(join(e,"package-lock.json"))||existsSync(join(e,"npm-shrinkwrap.json"))?{manager:"npm",source:"npm lockfile"}:{manager:"npm",source:"fallback"}},pn=e=>_(e)&&_(e.scripts)?Object.entries(e.scripts).filter(([,n])=>typeof n=="string").map(([n])=>n).sort():[],dn=(e,n)=>e.find(t=>n.some(o=>o.test(t))),un=(e,n)=>{let t=pn(n),o=["Cargo.toml","Cargo.lock","src-tauri/tauri.conf.json","apps/desktop/package.json","apps/desktop/src-tauri/tauri.conf.json","bridges/whatsmeow-bridge/Cargo.toml","apps","packages","bridges","turbo.json","nx.json","pnpm-workspace.yaml"].filter(s=>existsSync(join(e,s))),r=[];_(n)&&r.push("node"),o.some(s=>s==="Cargo.toml"||s==="Cargo.lock"||s.endsWith("Cargo.toml"))&&r.push("rust"),(o.some(s=>s.includes("tauri"))||t.some(s=>s.includes("tauri")))&&r.push("tauri"),(o.some(s=>s.startsWith("apps"))||t.some(s=>s.includes("desktop")))&&r.push("desktop"),(o.some(s=>s.startsWith("bridges"))||t.some(s=>s.includes("bridge")))&&r.push("bridge"),o.some(s=>["apps","packages","bridges","turbo.json","nx.json","pnpm-workspace.yaml"].includes(s))&&r.push("monorepo");let a=dn(t,[/^repo:check$/,/^check$/,/(^|:)check($|:)/,/(^|:)doctor($|:)/,/(^|:)health($|:)/,/(^|:)verify($|:)/,/(^|:)validate($|:)/,/(^|:)ci($|:)/]),l=[`stacks: ${r.length>0?[...new Set(r)].join(", "):"none detected"}`,`markers: ${o.length>0?o.slice(0,8).join(", "):"none"}`,a?`likely validation: npm run ${a}`:"likely validation: none detected"];return {id:"workspace.projectProfile",label:"Workspace project profile",status:r.length>0?"ok":"skip",message:l.join(". ")}},gn=e=>{if(!e)return {id:"envFile",label:"Agent env file",status:"skip",message:"Env file loading did not run."};if(e.disabled)return {id:"envFile",label:"Agent env file",status:"skip",message:"Env file loading disabled by --no-env-file."};if(e.errors.length>0)return {id:"envFile",label:"Agent env file",status:"fail",message:e.errors.join("; ")};if(e.paths.length===0)return {id:"envFile",label:"Agent env file",status:"skip",message:"No .brass-agent.env, .env.local, or .env found in workspace; using exported shell environment only."};let n=[`Loaded ${e.paths.join(", ")}`];return e.loadedKeys.length>0&&n.push(`keys: ${e.loadedKeys.join(", ")}`),e.alreadySetKeys.length>0&&n.push(`already set by shell: ${e.alreadySetKeys.join(", ")}`),e.emptyKeys.length>0&&n.push(`empty keys skipped: ${e.emptyKeys.join(", ")}`),e.ignoredKeys.length>0&&n.push(`non-agent keys ignored: ${e.ignoredKeys.slice(0,8).join(", ")}${e.ignoredKeys.length>8?", ...":""}`),e.invalidLines.length>0&&n.push(`invalid lines: ${e.invalidLines.join(", ")}`),{id:"envFile",label:"Agent env file",status:e.loadedKeys.length>0||e.alreadySetKeys.length>0?"ok":"warn",message:n.join(". ")}},fn=e=>{let n=(process.env.BRASS_LLM_PROVIDER??e?.llm?.provider)?.trim().toLowerCase(),t=e?.llm?.apiKeyEnv;if(n==="fake")return {id:"llm",label:"LLM provider",status:"ok",message:"Fake LLM provider is selected."};if(n==="google"||n==="gemini"){let o=L(t)||L("BRASS_GOOGLE_API_KEY")||L("GOOGLE_API_KEY")||L("GEMINI_API_KEY");return {id:"llm",label:"LLM provider",status:o?"ok":"fail",message:o?`Google/Gemini provider is configured (${n}).`:"Google/Gemini provider is selected but no API key env var is set. Export GEMINI_API_KEY or put it in .env/.brass-agent.env."}}if(n==="openai"||n==="openai-compatible"){let o=!!(process.env.BRASS_LLM_ENDPOINT??e?.llm?.endpoint),r=L(t)||L("BRASS_LLM_API_KEY");return {id:"llm",label:"LLM provider",status:o&&r?"ok":"fail",message:o&&r?`OpenAI-compatible provider is configured (${n}).`:"OpenAI-compatible provider is selected but endpoint or API key env var is missing. Export BRASS_LLM_API_KEY or put it in .env/.brass-agent.env."}}return L("BRASS_GOOGLE_API_KEY")||L("GOOGLE_API_KEY")||L("GEMINI_API_KEY")?{id:"llm",label:"LLM provider",status:"ok",message:"Google/Gemini credentials are available and will be auto-detected."}:(process.env.BRASS_LLM_ENDPOINT??e?.llm?.endpoint)&&L("BRASS_LLM_API_KEY")?{id:"llm",label:"LLM provider",status:"ok",message:"OpenAI-compatible credentials are available and will be auto-detected."}:{id:"llm",label:"LLM provider",status:"warn",message:"No real LLM credentials found. The CLI will fall back to the fake provider unless config selects a real provider."}},mn=e=>{let n=N(join(e,".vscode","settings.json"));if(!_(n))return;let t=n["brassAgent.command"];return typeof t=="string"?t:void 0},yn=e=>{try{return readdirSync(e).filter(n=>n.endsWith(".vsix")).sort().at(-1)}catch{return}},y=(e,n)=>{e.push(n);},_e=async e=>{let n=resolve(e.cwd),t=[],o=Le(n,"brass-runtime")??Le(process.cwd(),"brass-runtime"),r=Number(process.versions.node.split(".")[0]??"0");y(t,{id:"node",label:"Node.js",status:r>=18?"ok":"fail",message:`Node ${process.versions.node}${r>=18?"":" is too old; use Node 18 or newer."}`});let a=M("npm");y(t,{id:"npm",label:"npm",status:a?"ok":"fail",message:a?`npm ${a}`:"npm is not available on PATH."});let l=M("git");y(t,{id:"git",label:"git",status:l?"ok":"warn",message:l||"git is not available; patch apply/rollback uses git apply."});let s=M("rg");if(y(t,{id:"ripgrep",label:"ripgrep",status:s?"ok":"warn",message:s||"rg is not available; context discovery search will be limited."}),y(t,{id:"workspace",label:"Workspace",status:existsSync(n)?"ok":"fail",message:existsSync(n)?n:`Workspace does not exist: ${n}`}),e.workspaceDiscovery){let p=e.workspaceDiscovery;y(t,{id:"workspace.discovery",label:"Workspace discovery",status:p.disabled?"skip":p.marker?"ok":"warn",message:p.disabled?"Workspace discovery disabled by --no-discover-workspace.":p.marker?`${p.changed?`Resolved ${p.inputCwd} -> ${p.cwd}`:`Using ${p.cwd}`} via ${p.marker}.`:`No workspace marker found upward from ${p.inputCwd}; using input cwd.`});}let c=join(n,"package.json"),h=N(c);if(y(t,{id:"workspace.packageJson",label:"Workspace package.json",status:_(h)?"ok":"warn",message:_(h)?`Found ${c}`:"No package.json found in workspace; project command discovery may use fallbacks."}),_(h)){let p=_(h.scripts)?Object.keys(h.scripts):[];y(t,{id:"workspace.scripts",label:"Workspace scripts",status:p.length?"ok":"warn",message:p.length?`Scripts: ${p.slice(0,12).join(", ")}${p.length>12?", ...":""}`:"No package scripts found."});let g=cn(n),m=M(g.manager);y(t,{id:"workspace.packageManager",label:"Workspace package manager",status:m?"ok":"warn",message:m?`${g.manager} available (${g.source}): ${m}`:`${g.manager} inferred from ${g.source}, but command is not available on PATH.`});}if(y(t,un(n,h)),y(t,gn(e.envFileLoad)),y(t,fn(e.config)),y(t,{id:"config",label:"Agent config",status:e.configPath?"ok":"skip",message:e.configPath?`Loaded ${e.configPath}`:"No .brass-agent.json / brass-agent.config.json loaded; using built-in defaults and VS Code/CLI settings."}),e.includeVsCode!==false){let p=M(process.env.BRASS_CODE_CMD??"code");y(t,{id:"vscode.code",label:"VS Code CLI",status:p?"ok":"warn",message:p?`code ${p}`:"VS Code CLI `code` is not available on PATH; .vsix install needs it unless using the VS Code UI."});let g=mn(n),m=ln();y(t,{id:"vscode.settings",label:"VS Code extension setting",status:g?"ok":m?"skip":"warn",message:g?`brassAgent.command = ${g}`:m?`No workspace brassAgent.command needed; launched by the VS Code extension (${process.env.BRASS_AGENT_VSCODE_CLI_SOURCE??"auto"}).`:"No workspace .vscode/settings.json brassAgent.command found."});}if(o){let p=join(o,"src","agent","cli","main.ts"),g=join(o,"dist","agent","cli","main.cjs"),m=join(o,"extensions","vscode-brass-agent");y(t,{id:"repo.root",label:"brass-runtime repo",status:"ok",message:o}),y(t,{id:"repo.cliSource",label:"CLI source",status:existsSync(p)?"ok":"fail",message:existsSync(p)?`Found ${p}`:`Missing ${p}`}),y(t,{id:"repo.cliBuild",label:"CLI build",status:existsSync(g)?"ok":"warn",message:existsSync(g)?`Found ${g}`:"dist/agent/cli/main.cjs is missing; run npm run build."}),y(t,{id:"repo.extensionDir",label:"VS Code extension source",status:existsSync(join(m,"package.json"))?"ok":"warn",message:existsSync(join(m,"package.json"))?m:"extensions/vscode-brass-agent was not found."}),y(t,{id:"repo.extensionBuild",label:"VS Code extension build",status:existsSync(join(m,"out","extension.js"))?"ok":"warn",message:existsSync(join(m,"out","extension.js"))?"out/extension.js exists.":"Extension output missing; run npm run agent:vscode:package or compile the extension."});let S=yn(m);y(t,{id:"repo.extensionVsix",label:"VSIX package",status:S?"ok":"warn",message:S?`Found ${S}`:"No .vsix found in extensions/vscode-brass-agent."});let R=await access(g,constants.R_OK).then(()=>true).catch(()=>false);y(t,{id:"repo.cliReadable",label:"CLI artifact readable",status:R?"ok":"warn",message:R?"Built CLI artifact is readable.":"Built CLI artifact is not readable yet."});}else y(t,{id:"repo.root",label:"brass-runtime repo",status:"skip",message:"Not running inside a brass-runtime checkout; local source/build checks skipped."});let E=t.some(p=>p.status==="fail")?"fail":t.some(p=>p.status==="warn")?"warn":"ok";return {generatedAt:new Date().toISOString(),cwd:n,...e.configPath?{configPath:e.configPath}:{},...o?{repoRoot:o}:{},status:E,checks:t}},hn=e=>{switch(e){case "ok":return "\u2713";case "warn":return "!";case "fail":return "\u2717";case "skip":return "-"}},xe=e=>{console.log("brass-agent doctor"),console.log(`workspace: ${e.cwd}`),e.configPath&&console.log(`config: ${e.configPath}`),e.repoRoot&&console.log(`repo: ${e.repoRoot}`),console.log(`status: ${e.status}`),console.log("");for(let n of e.checks)console.log(`${hn(n.status)} ${n.label}: ${n.message}`);};var kn=[".brass-agent.env",".env.local",".env"],wn=new Set(["BRASS_LLM_PROVIDER","BRASS_FAKE_LLM_RESPONSE","BRASS_GOOGLE_API_KEY","GOOGLE_API_KEY","GEMINI_API_KEY","BRASS_GOOGLE_MODEL","BRASS_GOOGLE_API_VERSION","BRASS_GOOGLE_BASE_URL","BRASS_GOOGLE_ENDPOINT","BRASS_GOOGLE_SYSTEM_INSTRUCTION","BRASS_GOOGLE_TEMPERATURE","BRASS_GOOGLE_TOP_P","BRASS_GOOGLE_TOP_K","BRASS_GOOGLE_MAX_OUTPUT_TOKENS","BRASS_LLM_ENDPOINT","BRASS_LLM_API_KEY","BRASS_LLM_MODEL","BRASS_AGENT_APPROVAL","BRASS_AGENT_AUTO_APPROVE","BRASS_CODE_CMD"]),C=e=>Array.from(new Set(e)),Sn=e=>{if(e.length<2)return;let n=e[0];if(n!=='"'&&n!=="'"||e[e.length-1]!==n)return;let t=e.slice(1,-1);return n==="'"?t:t.replace(/\\n/g,`
7
- `).replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\")},En=e=>{let n=e.trim();if(!n||n.startsWith("#"))return {type:"skip"};let t=n.startsWith("export ")?n.slice(7).trimStart():n,o=/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(t);if(!o)return {type:"invalid"};let r=o[1]??"",l=(o[2]??"").trim(),c=Sn(l)??l.replace(/\s+#.*$/u,"").trim();return {type:"assignment",key:r,value:c}},Rn=(e,n)=>n?[isAbsolute(n)?n:resolve(e,n)]:kn.map(t=>join(e,t)),Ce=e=>{let n=resolve(e.cwd),t=e.allowedExtraKeys?.filter(Boolean)??[],o=new Set([...wn,...t]),r=e.noEnvFile?[]:Rn(n,e.envFile),a=[],l=[],s=[],c=[],h=[],E=[],p=[];if(e.noEnvFile)return {cwd:n,disabled:true,filesChecked:[],paths:[],loadedKeys:[],alreadySetKeys:[],emptyKeys:[],ignoredKeys:[],invalidLines:[],errors:[]};for(let g of r){if(!existsSync(g)){e.envFile&&p.push(`Env file does not exist: ${g}`);continue}a.push(g);let m;try{m=readFileSync(g,"utf8").replace(/^\uFEFF/u,"");}catch(S){p.push(`Could not read env file ${g}: ${S instanceof Error?S.message:String(S)}`);continue}for(let[S,R]of m.split(/\r?\n/gu).entries()){let b=En(R);if(b.type!=="skip"){if(b.type==="invalid"){E.push(`${g}:${S+1}`);continue}if(!o.has(b.key)){h.push(b.key);continue}if(!b.value){c.push(b.key);continue}if(process.env[b.key]!==void 0){s.push(b.key);continue}process.env[b.key]=b.value,l.push(b.key);}}}return {cwd:n,disabled:false,...e.envFile?{explicitPath:isAbsolute(e.envFile)?e.envFile:resolve(n,e.envFile)}:{},filesChecked:r,paths:a,loadedKeys:C(l),alreadySetKeys:C(s),emptyKeys:C(c),ignoredKeys:C(h),invalidLines:C(E),errors:C(p)}};var Fe=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),On=e=>{try{return JSON.parse(readFileSync(e,"utf8"))}catch{return}},Me=e=>{let n=On(join(e,"package.json"));return Fe(n)?n:void 0},$e=e=>{let n=e?.scripts;return Fe(n)?Object.entries(n).filter(([,t])=>typeof t=="string").map(([t])=>t).sort():[]},$=(e,n)=>n.some(t=>e.includes(t)),Cn=(e,n)=>{let t=typeof n?.packageManager=="string"?n.packageManager.split("@")[0]:void 0;return t==="npm"||t==="pnpm"||t==="yarn"||t==="bun"?t:existsSync(join(e,"pnpm-lock.yaml"))?"pnpm":existsSync(join(e,"yarn.lock"))?"yarn":existsSync(join(e,"bun.lockb"))||existsSync(join(e,"bun.lock"))?"bun":existsSync(join(e,"package-lock.json"))||existsSync(join(e,"npm-shrinkwrap.json"))?"npm":"auto"},In=e=>{switch(e){case "default":return;case "google":return {provider:"google",model:"gemini-2.5-flash",apiKeyEnv:"GEMINI_API_KEY",temperature:.2,maxOutputTokens:4096};case "openai-compatible":return {provider:"openai-compatible",endpoint:"https://api.openai.com/v1/chat/completions",model:"gpt-4.1",apiKeyEnv:"BRASS_LLM_API_KEY",temperature:.2};case "fake":return {provider:"fake",fakeResponse:"Fake plan from brass-agent init. Configure a real provider when ready."}}},Fn=(e,n)=>{let t=Me(e),o=$e(t),r=$(o,["typecheck","type-check","check-types","tsc","check"]),a=$(o,["lint","lint:ci"]),l=In(n);return {mode:"propose",approval:"auto",...l?{llm:l}:{},project:{packageManager:Cn(e,t),testScriptNames:["test","test:ci","test:unit"],includeTypecheck:r,includeLint:a,maxValidationCommands:2},context:{enabled:true,maxSearchQueries:3,maxFiles:4,maxSearchResults:40,globs:["*.ts","*.tsx","*.js","*.jsx","*.mjs","*.cjs","*.json","*.md","*.yml","*.yaml"],excludeGlobs:[".env*","**/.env*","**/node_modules/**","**/dist/**","**/build/**","**/.git/**","**/*.pem","**/*.key","**/secrets/**"]},patchQuality:{enabled:true,maxRepairAttempts:1},rollback:{enabled:true,onFinalValidationFailure:true,strategy:"all",maxRollbackDepth:8,runValidationAfterRollback:true,allowForSuppliedPatches:false},redaction:{enabled:true,additionalPatterns:[]},language:{response:"auto"},permissions:{shell:{inheritDefaults:true,ask:[{pattern:"npm run build",reason:"Build commands can be slow and may produce large outputs.",risk:"medium",defaultAnswer:"approve"},{pattern:"pnpm run build",reason:"Build commands can be slow and may produce large outputs.",risk:"medium",defaultAnswer:"approve"},{pattern:"yarn run build",reason:"Build commands can be slow and may produce large outputs.",risk:"medium",defaultAnswer:"approve"},{pattern:"bun run build",reason:"Build commands can be slow and may produce large outputs.",risk:"medium",defaultAnswer:"approve"}],deny:["rm *","git push *","git reset *","git clean *"]},patchApply:{decision:"ask",reason:"Apply the generated unified diff to the workspace.",risk:"high",defaultAnswer:"reject"}},tools:{"fs.readFile":{timeoutMs:1e4,retries:1},"fs.exists":{timeoutMs:5e3,retries:0},"fs.searchText":{timeoutMs:1e4,retries:1},"shell.exec":{timeoutMs:18e4,retries:0},"llm.complete":{timeoutMs:9e4,retries:2},"patch.apply":{timeoutMs:3e4,retries:0},"patch.rollback":{timeoutMs:3e4,retries:0}}}},Mn=e=>{let n=$e(Me(e)),t=[{preset:"inspect",mode:"read-only"}];return $(n,["typecheck","type-check","check-types","tsc","check"])&&t.push({preset:"typecheck",mode:"propose"}),$(n,["lint","lint:ci"])&&t.push({preset:"lint",mode:"propose"}),$(n,["test","test:ci","test:unit"])&&t.push({preset:"fix-tests",mode:"propose"}),t},$n=e=>`${JSON.stringify({stopOnFailure:false,goals:Mn(e)},null,2)}
8
- `,jn=e=>{let n=["# Brass Agent environment variables","# Copy this file to .env or .brass-agent.env, or export the variables in your shell.","# brass-agent auto-loads supported agent env keys from --cwd.","# Do not commit real API keys.",""];return e==="fake"&&n.push("BRASS_LLM_PROVIDER=fake","BRASS_FAKE_LLM_RESPONSE=Fake plan from .env.example",""),n.push("# Google / Gemini","# Used when .brass-agent.json selects provider google, or when auto-detected.","GEMINI_API_KEY=","BRASS_GOOGLE_MODEL=gemini-2.5-flash","","# OpenAI-compatible providers","# BRASS_LLM_PROVIDER=openai-compatible","BRASS_LLM_ENDPOINT=https://api.openai.com/v1/chat/completions","BRASS_LLM_API_KEY=","BRASS_LLM_MODEL=gpt-4.1","","# Approval behavior: auto | interactive | approve | deny","# BRASS_AGENT_APPROVAL=auto",""),e==="google"?n.splice(4,0,"BRASS_LLM_PROVIDER=google",""):e==="openai-compatible"&&n.splice(4,0,"BRASS_LLM_PROVIDER=openai-compatible",""),`${n.join(`
9
- `)}
10
- `},Bn=()=>["# Brass Agent","","This workspace was initialized with `brass-agent --init`.","","Generated files:","","- `.brass-agent.json` \u2014 local policy/config for Brass Agent.","- `brass-agent.batch.json` \u2014 sample multi-goal batch workflow.","- `.env.example` \u2014 example environment variables. Copy to `.env` or `.brass-agent.env`; keep real secrets out of git.","","Recommended first commands:","","```bash","brass-agent --doctor","brass-agent --preset inspect","brass-agent --batch-file brass-agent.batch.json","```","","Apply mode is intentionally approval-gated:","","```bash",'brass-agent --apply "fix the failing tests"',"```",""].join(`
11
- `),Dn=e=>{let n=["Review .brass-agent.json and adjust permissions/context budgets for this repo.","Run: brass-agent --doctor","Run: brass-agent --preset inspect","Run: brass-agent --batch-file brass-agent.batch.json"];return e==="google"?["Set GEMINI_API_KEY in your shell, `.env`, or `.brass-agent.env`.",...n]:e==="openai-compatible"?["Set BRASS_LLM_API_KEY and BRASS_LLM_ENDPOINT in your shell, `.env`, or `.brass-agent.env`.",...n]:e==="default"?["Set an LLM provider env var in your shell, `.env`, or `.brass-agent.env` when ready, or let the CLI fall back to fake mode.",...n]:n},T=async e=>{let n=resolve(e.cwd,e.relativePath),o=existsSync(n)?e.force?"overwritten":"skipped":"created";return !e.dryRun&&o!=="skipped"&&(await mkdir(dirname(n),{recursive:true}),await writeFile(n,e.content,"utf8")),{path:n,relativePath:e.relativePath,status:o,bytes:Buffer.byteLength(e.content,"utf8")}},je=async e=>{let n=resolve(e.cwd),t=e.profile??"default",o=e.force??false,r=e.dryRun??false,a=Fn(n,t),l=await Promise.all([T({cwd:n,relativePath:".brass-agent.json",content:`${JSON.stringify(a,null,2)}
12
- `,force:o,dryRun:r}),T({cwd:n,relativePath:"brass-agent.batch.json",content:$n(n),force:o,dryRun:r}),T({cwd:n,relativePath:".env.example",content:jn(t),force:o,dryRun:r}),T({cwd:n,relativePath:"BRASS_AGENT.md",content:Bn(),force:o,dryRun:r})]);return {cwd:n,profile:t,dryRun:r,files:l,nextSteps:Dn(t)}},Gn=e=>{switch(e){case "created":return "\u2713";case "overwritten":return "!";case "skipped":return "-"}},Be=e=>{console.log(`brass-agent init${e.dryRun?" (dry run)":""}`),console.log(`workspace: ${e.cwd}`),console.log(`profile: ${e.profile}`),console.log("");for(let n of e.files)console.log(`${Gn(n.status)} ${n.status} ${n.relativePath}`);console.log(""),console.log("next steps:");for(let n of e.nextSteps)console.log(`- ${n}`);};var F=new Function("specifier","return import(specifier)"),Nn=async(e,n)=>{let t=await F("node:path");return (await F("node:fs/promises")).readFile(t.resolve(e,n),"utf8")},K=e=>{if(e===void 0||e.trim()==="")return;let n=Number(e);return Number.isFinite(n)?n:void 0},Ye=e=>e==="read-only"||e==="propose"||e==="write"||e==="autonomous",Je=e=>e==="auto"||e==="interactive"||e==="approve"||e==="deny",De=e=>e==="default"||e==="google"||e==="openai-compatible"||e==="fake",Tn=e=>["auto","match-user","en","es","pt","fr","de","it","custom"].includes(e),k=(e,n,t)=>{let o=e[n],r=o.startsWith(`${t}=`)?o.slice(t.length+1):void 0;if(r!==void 0&&r!=="")return [r,n];let a=e[n+1];if(!a)throw new Error(`${t} requires a value`);return [a,n+1]},Kn=e=>{let n=process.cwd(),t=true,o=false,r="propose",a=false,l=false,s="human",c="auto",h=false,E,p=false,g,m=false,S=false,R,b="apply",V,re=false,se=false,W,Y,D,ae=false,ie=false,le=false,G="default",ce=false,J,U=[];for(let u=0;u<e.length;u+=1){let i=e[u];if(i==="--"){U.push(...e.slice(u+1));break}if(i==="--help"||i==="-h"){l=true;continue}if(i==="--doctor"){ae=true;continue}if(i==="--where"||i==="--print-workspace"){o=true;continue}if(i==="--no-discover-workspace"){t=false;continue}if(i==="--init"){ie=true;continue}if(i==="--force"||i==="--init-force"){le=true;continue}if(i==="--init-dry-run"){ce=true;continue}if(i==="--init-profile"||i.startsWith("--init-profile=")){let[d,f]=k(e,u,"--init-profile");if(!De(d))throw new Error("--init-profile requires one of: default, google, openai-compatible, fake");G=d,u=f;continue}if(i==="--init-provider"||i.startsWith("--init-provider=")){let[d,f]=k(e,u,"--init-provider");if(d==="auto")G="default";else if(De(d))G=d;else throw new Error("--init-provider requires one of: auto, fake, google, openai-compatible");u=f;continue}if(i==="--language"||i.startsWith("--language=")){let[d,f]=k(e,u,"--language");if(!Tn(d))throw new Error("--language requires one of: auto, match-user, en, es, pt, fr, de, it, custom");J=d,u=f;continue}if(i==="--json"){s="json";continue}if(i==="--events-json"){s="events-json";continue}if(i==="--protocol-json"){s="protocol-json";continue}if(i==="--protocol-full-patches"){S=true;continue}if(i==="--preset"||i.startsWith("--preset=")){let[d,f]=k(e,u,"--preset");if(!ga(d))throw new Error("--preset requires one of: fix-tests, inspect, typecheck, lint");W=d,u=f;continue}if(i==="--batch-file"||i.startsWith("--batch-file=")){let[d,f]=k(e,u,"--batch-file");Y=d,u=f;continue}if(i==="--batch-stop-on-failure"){D=true;continue}if(i==="--batch-continue-on-failure"){D=false;continue}if(i==="--ci"){re=true;continue}if(i==="--fail-on-patch-proposed"){se=true;continue}if(i==="--yes"||i==="-y"){c="approve",h=true;continue}if(i==="--no-input"){c="deny",h=true;continue}if(i==="--approval"||i.startsWith("--approval=")){let[d,f]=k(e,u,"--approval");if(!Je(d))throw new Error("--approval requires one of: auto, interactive, approve, deny");c=d,h=true,u=f;continue}if(i==="--apply"){r="write",a=true;continue}if(i==="--patch-file"||i.startsWith("--patch-file=")){let[d,f]=k(e,u,"--patch-file");R=d,u=f;continue}if(i==="--apply-patch-file"||i.startsWith("--apply-patch-file=")){let[d,f]=k(e,u,"--apply-patch-file");R=d,b="apply",r="write",a=true,u=f;continue}if(i==="--rollback-patch-file"||i.startsWith("--rollback-patch-file=")){let[d,f]=k(e,u,"--rollback-patch-file");R=d,b="rollback",r="write",a=true,u=f;continue}if(i==="--mode"||i.startsWith("--mode=")){let[d,f]=k(e,u,"--mode");if(!Ye(d))throw new Error("--mode requires one of: read-only, propose, write, autonomous");r=d,a=true,u=f;continue}if(i==="--cwd"||i.startsWith("--cwd=")){let[d,f]=k(e,u,"--cwd");n=d,u=f;continue}if(i==="--save-run"||i.startsWith("--save-run=")){let[d,f]=k(e,u,"--save-run");V=d,u=f;continue}if(i==="--config"||i.startsWith("--config=")){let[d,f]=k(e,u,"--config");E=d,p=false,u=f;continue}if(i==="--no-config"){E=void 0,p=true;continue}if(i==="--env-file"||i.startsWith("--env-file=")){let[d,f]=k(e,u,"--env-file");g=d,m=false,u=f;continue}if(i==="--no-env-file"){g=void 0,m=true;continue}if(i.startsWith("--"))throw new Error(`Unknown option: ${i}`);U.push(i);}return {cwd:n,discoverWorkspace:t,where:o,goalText:U.join(" ").trim(),mode:r,modeSpecified:a,showHelp:l,output:s,approval:c,approvalSpecified:h,...E?{configPath:E}:{},noConfig:p,...g?{envFile:g}:{},noEnvFile:m,protocolFullPatches:S,patchFileMode:b,ci:re,failOnPatchProposed:se,...W?{preset:W}:{},...Y?{batchFile:Y}:{},...D!==void 0?{batchStopOnFailure:D}:{},doctor:ae,init:ie,initForce:le,initProfile:G,initDryRun:ce,...J?{language:J}:{},...V?{saveRunDir:V}:{},...R?{patchFile:R}:{}}},Ue=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Vn=(e,n)=>{if(typeof e=="string")return e;if(!Ue(e))throw new Error(`${n} must be a string or object.`);let t=e.goal,o=e.preset,r=e.mode,a=e.cwd,l=e.patchFile,s=e.patchFileMode,c=e.saveRunDir;if(t!==void 0&&typeof t!="string")throw new Error(`${n}.goal must be a string.`);if(a!==void 0&&typeof a!="string")throw new Error(`${n}.cwd must be a string.`);if(l!==void 0&&typeof l!="string")throw new Error(`${n}.patchFile must be a string.`);if(c!==void 0&&typeof c!="string")throw new Error(`${n}.saveRunDir must be a string.`);if(o!==void 0&&(typeof o!="string"||!ga(o)))throw new Error(`${n}.preset must be one of: fix-tests, inspect, typecheck, lint.`);if(r!==void 0&&(typeof r!="string"||!Ye(r)))throw new Error(`${n}.mode must be one of: read-only, propose, write, autonomous.`);if(s!==void 0&&s!=="apply"&&s!=="rollback")throw new Error(`${n}.patchFileMode must be apply or rollback.`);if(t===void 0&&o===void 0&&l===void 0)throw new Error(`${n} must include goal, preset, or patchFile.`);return {...t?{goal:t}:{},...o?{preset:o}:{},...r?{mode:r}:{},...a?{cwd:a}:{},...l?{patchFile:l}:{},...s?{patchFileMode:s}:{},...c?{saveRunDir:c}:{}}},Wn=e=>{let n=Array.isArray(e)?e:Ue(e)&&Array.isArray(e.goals)?e.goals:void 0;if(!n)throw new Error("Batch file must be a JSON array or an object with a goals array.");return n.map((t,o)=>Vn(t,`goals[${o}]`))},Yn=async(e,n)=>{let t=await F("node:path"),o=await F("node:fs/promises"),r=t.isAbsolute(n)?n:t.resolve(e,n),a=String(await o.readFile(r,"utf8")).replace(/^\uFEFF/,"");try{return Wn(JSON.parse(a))}catch(l){if(!(l instanceof SyntaxError))throw l;let s=a.split(/\r?\n/g).map(c=>c.trim()).filter(c=>c&&!c.startsWith("#"));if(s.length===0)throw new Error(`Batch file has no goals: ${r}`);return s}},Jn=(e,n)=>typeof e=="string"?e:e.goal?e.goal:e.preset?ha(e.preset):e.patchFile??n?"apply supplied patch":"",Un=(e,n,t)=>typeof e!="string"&&e.mode?e.mode:typeof e!="string"&&e.preset==="inspect"&&!n.modeSpecified?"read-only":n.modeSpecified?n.mode:t.mode??n.mode,qn=(e,n,t)=>e.map((o,r)=>{let a=Jn(o,n.patchFile),l=typeof o=="string"?n.cwd:o.cwd??n.cwd,s=typeof o=="string"?n.patchFile:o.patchFile??n.patchFile,c=typeof o=="string"?n.patchFileMode:o.patchFileMode??n.patchFileMode,h=typeof o=="string"?n.saveRunDir:o.saveRunDir??n.saveRunDir;if(!a)throw new Error(`Batch goal ${r+1} resolved to an empty goal.`);return {index:r,cwd:l,goalText:a,mode:Un(o,n,t),patchFileMode:c,...s?{patchFile:s}:{},...h?{saveRunDir:h}:{}}}),Hn=async e=>{let n=ta(e.cwd,{enabled:e.discoverWorkspace}),t=n.cwd,o={...e,cwd:t},r=await pa({cwd:t,configPath:e.configPath,noConfig:e.noConfig}),a=Ce({cwd:t,envFile:e.envFile,noEnvFile:e.noEnvFile,allowedExtraKeys:r.config.llm?.apiKeyEnv?[r.config.llm.apiKeyEnv]:[]}),l=!e.goalText&&!e.preset&&!e.patchFile,s=e.batchFile?await Yn(t,e.batchFile):l?r.config.batch?.goals??[]:[],c=qn(s,o,r.config);return {...o,goalText:e.goalText||(e.preset?ha(e.preset):e.patchFile?"apply supplied patch":e.goalText),mode:e.modeSpecified?e.mode:e.preset==="inspect"?"read-only":r.config.mode??e.mode,approval:e.approvalSpecified?e.approval:r.config.approval??e.approval,config:r.config,workspaceDiscovery:n,batchRuns:c,batchStopOnFailureResolved:e.batchStopOnFailure??r.config.batch?.stopOnFailure??e.ci,envFileLoad:a,...r.path?{resolvedConfigPath:r.path}:{}}},Ge=()=>{console.log(['Usage: brass-agent [options] "goal"',"","Options:"," --mode read-only|propose|write|autonomous"," Agent permission mode. Default: propose, or config.mode if present."," --preset fix-tests|inspect|typecheck|lint"," Use a built-in goal preset when no explicit goal text is provided."," --apply"," Alias for --mode write."," --cwd PATH"," Starting directory for workspace discovery. Default: current directory."," --no-discover-workspace"," Use --cwd exactly instead of searching upward for package.json, .brass-agent.json, or .git."," --where, --print-workspace"," Print the resolved workspace root and exit."," --config PATH"," Load a specific .brass-agent.json policy/config file."," --save-run DIR"," Write final run JSON and Markdown artifacts to DIR."," --batch-file PATH"," Run multiple goals sequentially from a JSON or line-based file."," --batch-stop-on-failure"," Stop a batch after the first failed run."," --batch-continue-on-failure"," Continue a batch even when a run fails."," --doctor"," Check local CLI, workspace, VS Code, package manager, and LLM setup."," --init"," Initialize this workspace with .brass-agent.json, brass-agent.batch.json, .env.example, and BRASS_AGENT.md."," --force, --init-force"," Overwrite files generated by --init when they already exist."," --init-profile default|google|openai-compatible|fake"," Initialization profile. Default: default, which leaves provider auto-detection enabled."," --init-provider auto|fake|google|openai-compatible"," Alias for choosing an LLM-oriented init profile. auto maps to default."," --init-dry-run"," Preview generated files without writing them."," --no-config"," Do not discover or load an agent config file."," --language auto|match-user|en|es|pt|fr|de|it"," Response language for LLM summaries. Default: config.language or auto-match the user goal."," --env-file PATH"," Load Brass Agent environment variables from a specific env file."," --no-env-file"," Do not auto-load .brass-agent.env, .env.local, or .env from --cwd."," --json"," Print the full final AgentState JSON. Suppresses live event output."," --ci"," Preserve output mode but set process exit codes from the final run status."," --fail-on-patch-proposed"," In --ci mode, exit 2 when a patch was proposed but not applied."," --events-json"," Stream AgentEvent objects as JSON Lines. Does not print the final AgentState."," --protocol-json"," Stream Brass Agent protocol JSON Lines, including events and a final-state message."," --protocol-full-patches"," Keep patch payloads untruncated in protocol/event JSON output for trusted local integrations."," --patch-file PATH"," Supply a precomputed unified diff to the agent. Respects --mode."," --apply-patch-file PATH"," Supply and apply a precomputed unified diff. Alias for --patch-file PATH --mode write."," --rollback-patch-file PATH"," Reverse-apply a precomputed unified diff through PatchService. Requires write mode approvals."," --yes, -y"," Auto-approve approval prompts. Useful for CI and smoke tests."," --no-input"," Do not prompt; reject any action that requires approval."," --approval auto|interactive|approve|deny"," Approval strategy. Default: auto, or config.approval if present."," --help, -h"," Show this help message.","","Config files:"," brass-agent first resolves a workspace root by searching upward from --cwd."," It looks for .brass-agent.json, brass-agent.config.json, package.json, workspace markers, or .git."," brass-agent then searches upward from that workspace root for .brass-agent.json or brass-agent.config.json."," config.batch.goals can define a default batch when --batch-file is not provided.","","Examples:",' brass-agent "fix the failing tests"'," brass-agent --preset fix-tests"," brass-agent --preset inspect"," brass-agent --batch-file ./brass-agent.batch.json --ci"," brass-agent --where"," brass-agent --doctor"," brass-agent --doctor --json"," brass-agent --env-file .env --doctor"," brass-agent --init"," brass-agent --init --init-profile google"," brass-agent --init --init-profile fake --init-dry-run",' brass-agent --config ./agent.policy.json "fix the failing tests"',' brass-agent --no-config "fix the failing tests"',' brass-agent --json "fix the failing tests"',' brass-agent --events-json "fix the failing tests"',' brass-agent --protocol-json "fix the failing tests"',' brass-agent --protocol-json --protocol-full-patches "fix the failing tests"',' brass-agent --apply-patch-file ./approved.diff --yes "apply approved patch"',' brass-agent --apply "fix the failing tests"',' brass-agent --apply --yes "fix the failing tests"',' brass-agent --mode read-only --cwd ./repo "inspect the test failure"',"","LLM providers:"," BRASS_LLM_PROVIDER=fake"," BRASS_LLM_PROVIDER=google GEMINI_API_KEY=..."," BRASS_LLM_PROVIDER=openai-compatible BRASS_LLM_ENDPOINT=... BRASS_LLM_API_KEY=..."].join(`
13
- `));},qe=e=>e?process.env[e]:void 0,Ne=e=>{let n=qe(e?.apiKeyEnv)??process.env.BRASS_GOOGLE_API_KEY??process.env.GOOGLE_API_KEY??process.env.GEMINI_API_KEY;if(n)return ra({apiKey:n,model:process.env.BRASS_GOOGLE_MODEL??process.env.BRASS_LLM_MODEL??e?.model??"gemini-2.5-flash",apiVersion:process.env.BRASS_GOOGLE_API_VERSION??e?.apiVersion??"v1beta",baseUrl:process.env.BRASS_GOOGLE_BASE_URL??e?.baseUrl,endpoint:process.env.BRASS_GOOGLE_ENDPOINT??e?.endpoint,systemInstruction:process.env.BRASS_GOOGLE_SYSTEM_INSTRUCTION??e?.systemInstruction,temperature:K(process.env.BRASS_GOOGLE_TEMPERATURE)??e?.temperature,topP:K(process.env.BRASS_GOOGLE_TOP_P)??e?.topP,topK:K(process.env.BRASS_GOOGLE_TOP_K)??e?.topK,maxOutputTokens:K(process.env.BRASS_GOOGLE_MAX_OUTPUT_TOKENS)??e?.maxOutputTokens})},Te=e=>{let n=process.env.BRASS_LLM_ENDPOINT??e?.endpoint,t=qe(e?.apiKeyEnv)??process.env.BRASS_LLM_API_KEY,o=process.env.BRASS_LLM_MODEL??e?.model??"gpt-4.1";if(!(!n||!t))return qa({endpoint:n,apiKey:t,model:o})},zn=e=>{let n=(process.env.BRASS_LLM_PROVIDER??e?.provider)?.trim().toLowerCase(),t=process.env.BRASS_FAKE_LLM_RESPONSE??e?.fakeResponse;if(n==="fake")return sa({content:t});if(n==="google"||n==="gemini"){let o=Ne(e);if(!o)throw new Error("Google LLM provider requires BRASS_GOOGLE_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY, or config.llm.apiKeyEnv.");return o}if(n==="openai"||n==="openai-compatible"){let o=Te(e);if(!o)throw new Error("OpenAI-compatible LLM provider requires BRASS_LLM_ENDPOINT/config.llm.endpoint and BRASS_LLM_API_KEY/config.llm.apiKeyEnv.");return o}if(n)throw new Error(`Unsupported LLM provider: ${n}`);return Ne(e)??Te(e)??sa({content:t})},Qn=()=>{let e=process.env.BRASS_AGENT_APPROVAL?.trim().toLowerCase();if(e){if(Je(e))return e;throw new Error("BRASS_AGENT_APPROVAL must be one of: auto, interactive, approve, deny")}},Xn=e=>e==="1"||e?.toLowerCase()==="true"||e?.toLowerCase()==="yes",Zn=()=>!!(process.stdin?.isTTY&&(process.stderr?.isTTY??process.stdout?.isTTY)),et=e=>{if(e.approvalSpecified&&e.approval!=="auto")return e.approval;if(Xn(process.env.BRASS_AGENT_AUTO_APPROVE))return "approve";let n=Qn();return n&&n!=="auto"?n:e.approval!=="auto"?e.approval:e.output==="human"&&Zn()?"interactive":"deny"},nt=e=>{switch(et(e)){case "approve":return ka;case "deny":return la("Approval rejected because the CLI is running without interactive input. Use --yes to auto-approve.");case "interactive":return Pe()}},Ke=(e,n=2e3)=>e.length<=n?e:`${e.slice(0,n)}
14
- \u2026 truncated ${e.length-n} chars`,I=(e,n=1e3)=>e.length<=n?e:`${e.slice(0,n)}\u2026 truncated ${e.length-n} chars`,B=(e,n)=>n.fullPatches?e:I(e),He=(e,n)=>({...e,...e.initialPatch?{initialPatch:B(e.initialPatch,n)}:{}}),j=(e,n={})=>{switch(e.type){case "llm.complete":return {...e,prompt:I(e.prompt)};case "patch.apply":case "patch.rollback":case "patch.propose":return {...e,patch:B(e.patch,n)};default:return e}},ne=(e,n={})=>{switch(e.type){case "fs.fileRead":return {...e,content:I(e.content)};case "llm.response":return {...e,content:I(e.content)};case "shell.result":return {...e,stdout:I(e.stdout),stderr:I(e.stderr)};case "fs.searchResult":return {...e,matches:e.matches.slice(0,30),omittedMatches:Math.max(0,e.matches.length-30)};case "patch.proposed":return {...e,patch:B(e.patch,n)};case "patch.applied":case "patch.rolledBack":return e.patch?{...e,patch:B(e.patch,n)}:e;default:return e}},ze=(e,n={})=>{switch(e.type){case "agent.run.started":case "agent.run.completed":return {...e,goal:He(e.goal,n)};case "agent.action.started":return {...e,action:j(e.action,n)};case "agent.action.completed":return {...e,action:j(e.action,n),observation:ne(e.observation,n)};case "agent.action.failed":return {...e,action:j(e.action,n)};case "agent.observation.recorded":return {...e,observation:ne(e.observation,n)};case "agent.tool.timeout":case "agent.permission.denied":case "agent.approval.requested":case "agent.approval.resolved":return {...e,action:j(e.action,n)};default:return e}},te=(e,n={})=>({...e,goal:He(e.goal,n),observations:e.observations.map(t=>ne(t,n)),errors:e.errors.map(t=>{switch(t._tag){case "PermissionDenied":case "ApprovalRejected":return {...t,action:j(t.action,n)};case "PatchError":return {...t,cause:String(t.cause),...t.patch?{patch:B(t.patch,n)}:{}};case "FsError":case "ShellError":case "LLMError":return {...t,cause:String(t.cause)};default:return t}})}),oe=e=>({protocol:"brass-agent",version:1,...e}),tt=e=>{switch(e){case "ok":return "\u2713";case "warn":return "!";case "fail":return "\u2717"}},Ve=e=>`${Math.max(0,e)}ms`,w=(e,n)=>[...e.observations].reverse().find(t=>t.type===n),ot=e=>({emit(n){switch(n.type){case "agent.run.started":console.log(`brass-agent ${n.goal.mode}`),console.log(`workspace: ${n.goal.cwd}`),e&&console.log(`config: ${e}`),console.log(`goal: ${n.goal.text}`),console.log("");break;case "agent.action.started":console.log(`\u2192 ${T$1(n.action)}`);break;case "agent.action.completed":{let t=V(n.observation);console.log(`${tt(t)} ${U(n.observation)} ${Ve(n.durationMs)}`);break}case "agent.action.failed":n.error._tag!=="ToolTimeout"&&n.error._tag!=="PermissionDenied"&&n.error._tag!=="ApprovalRejected"&&console.log(`\u2717 ${T$1(n.action)} failed with ${n.error._tag} ${Ve(n.durationMs)}`);break;case "agent.tool.timeout":console.log(`! ${T$1(n.action)} timed out after ${n.timeoutMs}ms`);break;case "agent.permission.denied":console.log(`\u2717 ${T$1(n.action)} denied: ${n.reason}`);break;case "agent.approval.requested":console.log(`? approval required for ${T$1(n.action)} (${n.risk})`);break;case "agent.approval.resolved":n.approved?console.log(`\u2713 approval granted for ${T$1(n.action)}`):console.log(`\u2717 approval rejected for ${T$1(n.action)}${n.reason?`: ${n.reason}`:""}`);break;case "agent.patch.applied":break;case "agent.patch.rolledBack":n.automatic&&console.log(`\u2713 automatic rollback completed (${n.changedFiles.join(", ")||"no files reported"})`);break;}}}),rt=(e={})=>({emit(n){console.log(JSON.stringify(ze(n,e)));}}),st=(e={})=>({emit(n){console.log(JSON.stringify(oe({type:"event",event:ze(n,e)})));}}),We=e=>e.toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"run",ee=e=>e.replace(/\\/g,"\\\\").replace(/`/g,"\\`"),at=async(e,n,t)=>{let o=await F("node:path"),r=await F("node:fs/promises"),a=o.isAbsolute(n)?n:o.resolve(e.goal.cwd,n);await r.mkdir(a,{recursive:true});let s=`${We(e.goal.id)}-${We(e.goal.text)}`,c=o.join(a,`${s}.json`),h=o.join(a,`${s}.md`),E=w(e,"agent.done"),p=w(e,"agent.error"),g=w(e,"patch.applied"),m=w(e,"patch.rolledBack");await r.writeFile(c,`${JSON.stringify(te(e,t),null,2)}
15
- `,"utf8"),await r.writeFile(h,[`# Brass Agent Run ${ee(e.goal.id)}`,"",`- Goal: ${ee(e.goal.text)}`,`- Workspace: ${ee(e.goal.cwd)}`,`- Mode: ${e.goal.mode}`,`- Phase: ${e.phase}`,`- Steps: ${e.steps}`,g?`- Changed files: ${g.changedFiles.join(", ")||"none reported"}`:void 0,m?`- Rolled back files: ${m.changedFiles.join(", ")||"none reported"}`:void 0,"","## Summary","",E?.summary?.trim()||(p?`Error: ${p.error._tag}`:"No summary recorded.")].filter(Boolean).join(`
16
- `),"utf8"),process.stderr?.isTTY&&console.error(`saved run artifacts: ${c} ${h}`);},it=e=>w(e,"shell.result"),lt=(e,n)=>{if(w(e,"agent.error"))return 1;let t=it(e);return t&&t.exitCode!==0?1:n.failOnPatchProposed&&w(e,"patch.proposed")&&!w(e,"patch.applied")?2:0},ct=e=>{let n=w(e,"agent.done"),t=w(e,"agent.error"),o=w(e,"patch.proposed"),r=w(e,"patch.applied"),a=w(e,"patch.rolledBack"),l=w(e,"llm.response");console.log(""),console.log(`phase: ${e.phase}`),console.log(`steps: ${e.steps}`),a?console.log(`rolled back files: ${a.changedFiles.join(", ")||"(none reported)"}`):r?console.log(`changed files: ${r.changedFiles.join(", ")||"(none reported)"}`):o&&console.log("patch: proposed only; rerun with --apply to apply it"),n?(console.log(""),console.log("summary:"),console.log(Ke(n.summary.trim()||"Agent completed."))):t?(console.log(""),console.log("error:"),console.log(JSON.stringify(t.error,null,2))):l&&(console.log(""),console.log("llm response:"),console.log(Ke(l.content.trim())));},pt=(e,n)=>e.output==="human"?ot(e.resolvedConfigPath):e.output==="events-json"?rt(n):e.output==="protocol-json"?st(n):void 0,dt=(e,n)=>{let t=ma;return {shell:t,fs:na(t),patch:oa(t),llm:zn(e.config.llm),permissions:ia(e.config.permissions),approvals:nt(e),...n?{events:n}:{},...e.config.tools?{toolPolicies:e.config.tools}:{}}},ut=e=>({index:0,cwd:e.cwd,goalText:e.goalText,mode:e.mode,patchFileMode:e.patchFileMode,...e.patchFile?{patchFile:e.patchFile}:{},...e.saveRunDir?{saveRunDir:e.saveRunDir}:{}}),gt=async(e,n,t,o)=>{let r=dt(e,o),a=new M$1({env:r}),l=n.patchFile?await Nn(n.cwd,n.patchFile):void 0,s=await a.toPromise(ba(a,{id:`agent-${Date.now()}-${n.index+1}`,cwd:n.cwd,text:n.goalText,mode:n.mode,...e.config.project?{project:e.config.project}:{},...e.config.context?{context:e.config.context}:{},...e.config.patchQuality?{patchQuality:e.config.patchQuality}:{},...e.config.rollback?{rollback:e.config.rollback}:{},...e.config.redaction?{redaction:e.config.redaction}:{},...e.language?{language:{response:e.language}}:e.config.language?{language:e.config.language}:{},...l?{initialPatch:l,initialPatchMode:n.patchFileMode}:{}}));return n.saveRunDir&&await at(s,n.saveRunDir,t),{run:n,state:s,exitCode:lt(s,{failOnPatchProposed:e.failOnPatchProposed})}},ft=e=>e.some(n=>n.exitCode===1)?1:e.some(n=>n.exitCode===2)?2:0,Qe=(e,n)=>({total:e.length,completed:n.length,failed:n.filter(t=>t.exitCode!==0).length,exitCode:ft(n),stoppedEarly:n.length<e.length}),mt=(e,n)=>{let t=Qe(e,n);console.log(""),console.log("batch summary:"),console.log(`completed: ${t.completed}/${t.total}`),console.log(`failed: ${t.failed}`),t.stoppedEarly&&console.log("stopped early: yes"),console.log(`exit code: ${t.exitCode}`);},yt=e=>{let n={cwd:e.cwd,inputCwd:e.workspaceDiscovery.inputCwd,changed:e.workspaceDiscovery.changed,disabled:!!e.workspaceDiscovery.disabled,marker:e.workspaceDiscovery.marker,markerPath:e.workspaceDiscovery.markerPath,configPath:e.resolvedConfigPath,envFiles:e.envFileLoad.paths};if(e.output==="json"||e.output==="protocol-json"){console.log(JSON.stringify(n,null,2));return}console.log("brass-agent workspace"),console.log(`input: ${n.inputCwd}`),console.log(`workspace: ${n.cwd}`),n.disabled?console.log("discovery: disabled"):n.marker?console.log(`marker: ${n.marker} (${n.markerPath})`):console.log("marker: none found; using input cwd"),n.configPath&&console.log(`config: ${n.configPath}`),n.envFiles.length>0&&console.log(`env: ${n.envFiles.join(", ")}`);},ht=async()=>{let e=await Hn(Kn(process.argv.slice(2))),n=e.batchRuns.length>0;if(e.showHelp&&(Ge(),process.exit(0)),e.where){yt(e);return}if(e.init){let s=await je({cwd:e.cwd,force:e.initForce,dryRun:e.initDryRun,profile:e.initProfile});e.output==="json"?console.log(JSON.stringify(s,null,2)):Be(s);return}if(e.doctor){let s=await _e({cwd:e.cwd,config:e.config,configPath:e.resolvedConfigPath,envFileLoad:e.envFileLoad,workspaceDiscovery:e.workspaceDiscovery});e.output==="json"?console.log(JSON.stringify(s,null,2)):xe(s),process.exitCode=s.status==="fail"?1:0;return}!e.goalText&&!n&&(Ge(),process.exit(1));let t={fullPatches:e.protocolFullPatches},o=pt(e,t),r=n?e.batchRuns:[ut(e)],a=[];for(let s of r){let c=await gt(e,s,t,o);if(a.push(c),e.output==="protocol-json"?console.log(JSON.stringify(oe({type:"final-state",state:te(c.state,t)}))):e.output==="human"&&ct(c.state),n&&e.batchStopOnFailureResolved&&c.exitCode!==0)break}if(n){let s=Qe(r,a);e.output==="json"?console.log(JSON.stringify({type:"batch",summary:s,results:a.map(c=>({index:c.run.index,goal:c.run.goalText,cwd:c.run.cwd,mode:c.run.mode,exitCode:c.exitCode,state:te(c.state,t)}))},null,2)):e.output==="protocol-json"?console.log(JSON.stringify(oe({type:"batch-summary",summary:s}))):e.output==="human"&&mt(r,a),e.ci&&(process.exitCode=s.exitCode);return}let l=a[0];if(!l)throw new Error("Agent run did not produce a result.");e.output==="json"&&console.log(JSON.stringify(l.state,null,2)),e.ci&&(process.exitCode=l.exitCode);};ht().catch(e=>{console.error(e),process.exit(1);});
2
+ "use strict"; function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+ var _chunkHRVX2IYWjs = require('../../chunk-HRVX2IYW.js');
21
+
22
+
23
+
24
+
25
+
26
+ var _chunkTGOMLZ65js = require('../../chunk-TGOMLZ65.js');
27
+
28
+ // src/agent/cli/approvals.ts
29
+ var dynamicImport = new Function("specifier", "return import(specifier)");
30
+ var isYes = (value) => ["y", "yes", "approve", "approved", "si", "s\xED", "s"].includes(value);
31
+ var isNo = (value) => ["n", "no", "reject", "rejected", "deny", "denied"].includes(value);
32
+ var answerToResponse = (answer, request) => {
33
+ const normalized = answer.trim().toLowerCase();
34
+ if (!normalized) {
35
+ return request.defaultAnswer === "approve" ? { type: "approved" } : { type: "rejected", reason: "Rejected by default answer." };
36
+ }
37
+ if (isYes(normalized)) return { type: "approved" };
38
+ if (isNo(normalized)) return { type: "rejected", reason: "Rejected by user." };
39
+ return { type: "rejected", reason: `Unrecognized approval answer: ${answer}` };
40
+ };
41
+ var makeCliApprovalService = (options = {}) => ({
42
+ request: (request) => _chunkTGOMLZ65js.async.call(void 0, (_env, cb) => {
43
+ let closed = false;
44
+ let rl;
45
+ dynamicImport("node:readline/promises").then(({ createInterface }) => {
46
+ if (closed) return void 0;
47
+ const input = _nullishCoalesce(options.input, () => ( process.stdin));
48
+ const output = _nullishCoalesce(_nullishCoalesce(options.output, () => ( process.stderr)), () => ( process.stdout));
49
+ const defaultHint = request.defaultAnswer === "approve" ? "Y/n" : "y/N";
50
+ rl = createInterface({ input, output });
51
+ _optionalChain([output, 'optionalAccess', _ => _.write, 'optionalCall', _2 => _2(`
52
+ Approval required (${request.risk})
53
+ `)]);
54
+ _optionalChain([output, 'optionalAccess', _3 => _3.write, 'optionalCall', _4 => _4(`Action: ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, request.action)}
55
+ `)]);
56
+ _optionalChain([output, 'optionalAccess', _5 => _5.write, 'optionalCall', _6 => _6(`Reason: ${request.reason}
57
+ `)]);
58
+ return rl.question(`Approve? [${defaultHint}] `);
59
+ }).then((answer) => {
60
+ if (answer === void 0 || closed) return;
61
+ closed = true;
62
+ _optionalChain([rl, 'optionalAccess', _7 => _7.close, 'optionalCall', _8 => _8()]);
63
+ cb(_chunkTGOMLZ65js.Exit.succeed(answerToResponse(answer, request)));
64
+ }).catch((cause) => {
65
+ if (closed) return;
66
+ closed = true;
67
+ _optionalChain([rl, 'optionalAccess', _9 => _9.close, 'optionalCall', _10 => _10()]);
68
+ cb(
69
+ _chunkTGOMLZ65js.Exit.failCause(
70
+ _chunkTGOMLZ65js.Cause.fail({
71
+ _tag: "AgentLoopError",
72
+ message: `Approval prompt failed: ${String(cause)}`
73
+ })
74
+ )
75
+ );
76
+ });
77
+ return () => {
78
+ if (closed) return;
79
+ closed = true;
80
+ _optionalChain([rl, 'optionalAccess', _11 => _11.close, 'optionalCall', _12 => _12()]);
81
+ };
82
+ })
83
+ });
84
+
85
+ // src/agent/cli/doctor.ts
86
+ var _fs = require('fs');
87
+ var _promises = require('fs/promises');
88
+ var _path = require('path');
89
+ var _child_process = require('child_process');
90
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
91
+ var readJsonFile = (path) => {
92
+ try {
93
+ return JSON.parse(_fs.readFileSync.call(void 0, path, "utf8"));
94
+ } catch (e) {
95
+ return void 0;
96
+ }
97
+ };
98
+ var findPackageRoot = (start, packageName) => {
99
+ let current = _path.resolve.call(void 0, start);
100
+ while (true) {
101
+ const packageJsonPath = _path.join.call(void 0, current, "package.json");
102
+ const json = readJsonFile(packageJsonPath);
103
+ if (isRecord(json) && json.name === packageName) return current;
104
+ const parent = _path.resolve.call(void 0, current, "..");
105
+ if (parent === current) return void 0;
106
+ current = parent;
107
+ }
108
+ };
109
+ var commandVersion = (command, args = ["--version"], cwd) => {
110
+ const result = _child_process.spawnSync.call(void 0, command, [...args], {
111
+ cwd,
112
+ encoding: "utf8",
113
+ shell: false,
114
+ timeout: 1e4
115
+ });
116
+ if (result.error || (_nullishCoalesce(result.status, () => ( 0))) !== 0) return void 0;
117
+ return `${_nullishCoalesce(result.stdout, () => ( ""))}${_nullishCoalesce(result.stderr, () => ( ""))}`.trim().split(/\r?\n/g)[0];
118
+ };
119
+ var hasEnv = (name) => Boolean(name && process.env[name]);
120
+ var launchedFromVsCodeExtension = () => process.env.BRASS_AGENT_VSCODE_EXTENSION === "1";
121
+ var packageManagerFromWorkspace = (cwd) => {
122
+ const packageJson = readJsonFile(_path.join.call(void 0, cwd, "package.json"));
123
+ const packageManager = isRecord(packageJson) && typeof packageJson.packageManager === "string" ? packageJson.packageManager.split("@")[0] : void 0;
124
+ if (packageManager) return { manager: packageManager, source: "package.json packageManager" };
125
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "pnpm-lock.yaml"))) return { manager: "pnpm", source: "pnpm-lock.yaml" };
126
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "yarn.lock"))) return { manager: "yarn", source: "yarn.lock" };
127
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "bun.lockb")) || _fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "bun.lock"))) return { manager: "bun", source: "bun lockfile" };
128
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "package-lock.json")) || _fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "npm-shrinkwrap.json"))) return { manager: "npm", source: "npm lockfile" };
129
+ return { manager: "npm", source: "fallback" };
130
+ };
131
+ var packageScripts = (packageJson) => isRecord(packageJson) && isRecord(packageJson.scripts) ? Object.entries(packageJson.scripts).filter(([, value]) => typeof value === "string").map(([name]) => name).sort() : [];
132
+ var hasScriptMatching = (scripts, patterns) => scripts.find((name) => patterns.some((pattern) => pattern.test(name)));
133
+ var projectProfileCheck = (cwd, packageJson) => {
134
+ const scripts = packageScripts(packageJson);
135
+ const markers = [
136
+ "Cargo.toml",
137
+ "Cargo.lock",
138
+ "src-tauri/tauri.conf.json",
139
+ "apps/desktop/package.json",
140
+ "apps/desktop/src-tauri/tauri.conf.json",
141
+ "bridges/whatsmeow-bridge/Cargo.toml",
142
+ "apps",
143
+ "packages",
144
+ "bridges",
145
+ "turbo.json",
146
+ "nx.json",
147
+ "pnpm-workspace.yaml"
148
+ ].filter((path) => _fs.existsSync.call(void 0, _path.join.call(void 0, cwd, path)));
149
+ const stacks = [];
150
+ if (isRecord(packageJson)) stacks.push("node");
151
+ if (markers.some((marker) => marker === "Cargo.toml" || marker === "Cargo.lock" || marker.endsWith("Cargo.toml"))) stacks.push("rust");
152
+ if (markers.some((marker) => marker.includes("tauri")) || scripts.some((name) => name.includes("tauri"))) stacks.push("tauri");
153
+ if (markers.some((marker) => marker.startsWith("apps")) || scripts.some((name) => name.includes("desktop"))) stacks.push("desktop");
154
+ if (markers.some((marker) => marker.startsWith("bridges")) || scripts.some((name) => name.includes("bridge"))) stacks.push("bridge");
155
+ if (markers.some((marker) => ["apps", "packages", "bridges", "turbo.json", "nx.json", "pnpm-workspace.yaml"].includes(marker))) stacks.push("monorepo");
156
+ const healthScript = hasScriptMatching(scripts, [
157
+ /^repo:check$/,
158
+ /^check$/,
159
+ /(^|:)check($|:)/,
160
+ /(^|:)doctor($|:)/,
161
+ /(^|:)health($|:)/,
162
+ /(^|:)verify($|:)/,
163
+ /(^|:)validate($|:)/,
164
+ /(^|:)ci($|:)/
165
+ ]);
166
+ const pieces = [
167
+ `stacks: ${stacks.length > 0 ? [...new Set(stacks)].join(", ") : "none detected"}`,
168
+ `markers: ${markers.length > 0 ? markers.slice(0, 8).join(", ") : "none"}`,
169
+ healthScript ? `likely validation: npm run ${healthScript}` : "likely validation: none detected"
170
+ ];
171
+ return {
172
+ id: "workspace.projectProfile",
173
+ label: "Workspace project profile",
174
+ status: stacks.length > 0 ? "ok" : "skip",
175
+ message: pieces.join(". ")
176
+ };
177
+ };
178
+ var envFileCheck = (load) => {
179
+ if (!load) {
180
+ return {
181
+ id: "envFile",
182
+ label: "Agent env file",
183
+ status: "skip",
184
+ message: "Env file loading did not run."
185
+ };
186
+ }
187
+ if (load.disabled) {
188
+ return {
189
+ id: "envFile",
190
+ label: "Agent env file",
191
+ status: "skip",
192
+ message: "Env file loading disabled by --no-env-file."
193
+ };
194
+ }
195
+ if (load.errors.length > 0) {
196
+ return {
197
+ id: "envFile",
198
+ label: "Agent env file",
199
+ status: "fail",
200
+ message: load.errors.join("; ")
201
+ };
202
+ }
203
+ if (load.paths.length === 0) {
204
+ return {
205
+ id: "envFile",
206
+ label: "Agent env file",
207
+ status: "skip",
208
+ message: "No .brass-agent.env, .env.local, or .env found in workspace; using exported shell environment only."
209
+ };
210
+ }
211
+ const pieces = [`Loaded ${load.paths.join(", ")}`];
212
+ if (load.loadedKeys.length > 0) pieces.push(`keys: ${load.loadedKeys.join(", ")}`);
213
+ if (load.alreadySetKeys.length > 0) pieces.push(`already set by shell: ${load.alreadySetKeys.join(", ")}`);
214
+ if (load.emptyKeys.length > 0) pieces.push(`empty keys skipped: ${load.emptyKeys.join(", ")}`);
215
+ if (load.ignoredKeys.length > 0) pieces.push(`non-agent keys ignored: ${load.ignoredKeys.slice(0, 8).join(", ")}${load.ignoredKeys.length > 8 ? ", ..." : ""}`);
216
+ if (load.invalidLines.length > 0) pieces.push(`invalid lines: ${load.invalidLines.join(", ")}`);
217
+ return {
218
+ id: "envFile",
219
+ label: "Agent env file",
220
+ status: load.loadedKeys.length > 0 || load.alreadySetKeys.length > 0 ? "ok" : "warn",
221
+ message: pieces.join(". ")
222
+ };
223
+ };
224
+ var llmCheck = (config) => {
225
+ const provider = _optionalChain([(_nullishCoalesce(process.env.BRASS_LLM_PROVIDER, () => ( _optionalChain([config, 'optionalAccess', _13 => _13.llm, 'optionalAccess', _14 => _14.provider])))), 'optionalAccess', _15 => _15.trim, 'call', _16 => _16(), 'access', _17 => _17.toLowerCase, 'call', _18 => _18()]);
226
+ const configuredApiKeyEnv = _optionalChain([config, 'optionalAccess', _19 => _19.llm, 'optionalAccess', _20 => _20.apiKeyEnv]);
227
+ if (provider === "fake") {
228
+ return {
229
+ id: "llm",
230
+ label: "LLM provider",
231
+ status: "ok",
232
+ message: "Fake LLM provider is selected."
233
+ };
234
+ }
235
+ if (provider === "google" || provider === "gemini") {
236
+ const keyPresent = hasEnv(configuredApiKeyEnv) || hasEnv("BRASS_GOOGLE_API_KEY") || hasEnv("GOOGLE_API_KEY") || hasEnv("GEMINI_API_KEY");
237
+ return {
238
+ id: "llm",
239
+ label: "LLM provider",
240
+ status: keyPresent ? "ok" : "fail",
241
+ message: keyPresent ? `Google/Gemini provider is configured (${provider}).` : "Google/Gemini provider is selected but no API key env var is set. Export GEMINI_API_KEY or put it in .env/.brass-agent.env."
242
+ };
243
+ }
244
+ if (provider === "openai" || provider === "openai-compatible") {
245
+ const endpointPresent = Boolean(_nullishCoalesce(process.env.BRASS_LLM_ENDPOINT, () => ( _optionalChain([config, 'optionalAccess', _21 => _21.llm, 'optionalAccess', _22 => _22.endpoint]))));
246
+ const keyPresent = hasEnv(configuredApiKeyEnv) || hasEnv("BRASS_LLM_API_KEY");
247
+ return {
248
+ id: "llm",
249
+ label: "LLM provider",
250
+ status: endpointPresent && keyPresent ? "ok" : "fail",
251
+ message: endpointPresent && keyPresent ? `OpenAI-compatible provider is configured (${provider}).` : "OpenAI-compatible provider is selected but endpoint or API key env var is missing. Export BRASS_LLM_API_KEY or put it in .env/.brass-agent.env."
252
+ };
253
+ }
254
+ if (hasEnv("BRASS_GOOGLE_API_KEY") || hasEnv("GOOGLE_API_KEY") || hasEnv("GEMINI_API_KEY")) {
255
+ return {
256
+ id: "llm",
257
+ label: "LLM provider",
258
+ status: "ok",
259
+ message: "Google/Gemini credentials are available and will be auto-detected."
260
+ };
261
+ }
262
+ if ((_nullishCoalesce(process.env.BRASS_LLM_ENDPOINT, () => ( _optionalChain([config, 'optionalAccess', _23 => _23.llm, 'optionalAccess', _24 => _24.endpoint])))) && hasEnv("BRASS_LLM_API_KEY")) {
263
+ return {
264
+ id: "llm",
265
+ label: "LLM provider",
266
+ status: "ok",
267
+ message: "OpenAI-compatible credentials are available and will be auto-detected."
268
+ };
269
+ }
270
+ return {
271
+ id: "llm",
272
+ label: "LLM provider",
273
+ status: "warn",
274
+ message: "No real LLM credentials found. The CLI will fall back to the fake provider unless config selects a real provider."
275
+ };
276
+ };
277
+ var workspaceSettingsCommand = (cwd) => {
278
+ const settings = readJsonFile(_path.join.call(void 0, cwd, ".vscode", "settings.json"));
279
+ if (!isRecord(settings)) return void 0;
280
+ const command = settings["brassAgent.command"];
281
+ return typeof command === "string" ? command : void 0;
282
+ };
283
+ var newestVsix = (extensionDir) => {
284
+ try {
285
+ return _fs.readdirSync.call(void 0, extensionDir).filter((name) => name.endsWith(".vsix")).sort().at(-1);
286
+ } catch (e2) {
287
+ return void 0;
288
+ }
289
+ };
290
+ var push = (checks, check) => {
291
+ checks.push(check);
292
+ };
293
+ var runAgentDoctor = _chunkTGOMLZ65js.async.call(void 0, options) => {
294
+ const cwd = _path.resolve.call(void 0, options.cwd);
295
+ const checks = [];
296
+ const repoRoot = _nullishCoalesce(findPackageRoot(cwd, "brass-runtime"), () => ( findPackageRoot(process.cwd(), "brass-runtime")));
297
+ const nodeMajor = Number(_nullishCoalesce(process.versions.node.split(".")[0], () => ( "0")));
298
+ push(checks, {
299
+ id: "node",
300
+ label: "Node.js",
301
+ status: nodeMajor >= 18 ? "ok" : "fail",
302
+ message: `Node ${process.versions.node}${nodeMajor >= 18 ? "" : " is too old; use Node 18 or newer."}`
303
+ });
304
+ const npmVersion = commandVersion("npm");
305
+ push(checks, {
306
+ id: "npm",
307
+ label: "npm",
308
+ status: npmVersion ? "ok" : "fail",
309
+ message: npmVersion ? `npm ${npmVersion}` : "npm is not available on PATH."
310
+ });
311
+ const gitVersion = commandVersion("git");
312
+ push(checks, {
313
+ id: "git",
314
+ label: "git",
315
+ status: gitVersion ? "ok" : "warn",
316
+ message: gitVersion ? gitVersion : "git is not available; patch apply/rollback uses git apply."
317
+ });
318
+ const rgVersion = commandVersion("rg");
319
+ push(checks, {
320
+ id: "ripgrep",
321
+ label: "ripgrep",
322
+ status: rgVersion ? "ok" : "warn",
323
+ message: rgVersion ? rgVersion : "rg is not available; context discovery search will be limited."
324
+ });
325
+ push(checks, {
326
+ id: "workspace",
327
+ label: "Workspace",
328
+ status: _fs.existsSync.call(void 0, cwd) ? "ok" : "fail",
329
+ message: _fs.existsSync.call(void 0, cwd) ? cwd : `Workspace does not exist: ${cwd}`
330
+ });
331
+ if (options.workspaceDiscovery) {
332
+ const discovery = options.workspaceDiscovery;
333
+ push(checks, {
334
+ id: "workspace.discovery",
335
+ label: "Workspace discovery",
336
+ status: discovery.disabled ? "skip" : discovery.marker ? "ok" : "warn",
337
+ message: discovery.disabled ? "Workspace discovery disabled by --no-discover-workspace." : discovery.marker ? `${discovery.changed ? `Resolved ${discovery.inputCwd} -> ${discovery.cwd}` : `Using ${discovery.cwd}`} via ${discovery.marker}.` : `No workspace marker found upward from ${discovery.inputCwd}; using input cwd.`
338
+ });
339
+ }
340
+ const packageJsonPath = _path.join.call(void 0, cwd, "package.json");
341
+ const packageJson = readJsonFile(packageJsonPath);
342
+ push(checks, {
343
+ id: "workspace.packageJson",
344
+ label: "Workspace package.json",
345
+ status: isRecord(packageJson) ? "ok" : "warn",
346
+ message: isRecord(packageJson) ? `Found ${packageJsonPath}` : "No package.json found in workspace; project command discovery may use fallbacks."
347
+ });
348
+ if (isRecord(packageJson)) {
349
+ const scripts = isRecord(packageJson.scripts) ? Object.keys(packageJson.scripts) : [];
350
+ push(checks, {
351
+ id: "workspace.scripts",
352
+ label: "Workspace scripts",
353
+ status: scripts.length ? "ok" : "warn",
354
+ message: scripts.length ? `Scripts: ${scripts.slice(0, 12).join(", ")}${scripts.length > 12 ? ", ..." : ""}` : "No package scripts found."
355
+ });
356
+ const pm = packageManagerFromWorkspace(cwd);
357
+ const pmVersion = commandVersion(pm.manager);
358
+ push(checks, {
359
+ id: "workspace.packageManager",
360
+ label: "Workspace package manager",
361
+ status: pmVersion ? "ok" : "warn",
362
+ message: pmVersion ? `${pm.manager} available (${pm.source}): ${pmVersion}` : `${pm.manager} inferred from ${pm.source}, but command is not available on PATH.`
363
+ });
364
+ }
365
+ push(checks, projectProfileCheck(cwd, packageJson));
366
+ push(checks, envFileCheck(options.envFileLoad));
367
+ push(checks, llmCheck(options.config));
368
+ push(checks, {
369
+ id: "config",
370
+ label: "Agent config",
371
+ status: options.configPath ? "ok" : "skip",
372
+ message: options.configPath ? `Loaded ${options.configPath}` : "No .brass-agent.json / brass-agent.config.json loaded; using built-in defaults and VS Code/CLI settings."
373
+ });
374
+ if (options.includeVsCode !== false) {
375
+ const codeVersion = commandVersion(_nullishCoalesce(process.env.BRASS_CODE_CMD, () => ( "code")));
376
+ push(checks, {
377
+ id: "vscode.code",
378
+ label: "VS Code CLI",
379
+ status: codeVersion ? "ok" : "warn",
380
+ message: codeVersion ? `code ${codeVersion}` : "VS Code CLI `code` is not available on PATH; .vsix install needs it unless using the VS Code UI."
381
+ });
382
+ const configured = workspaceSettingsCommand(cwd);
383
+ const launchedByExtension = launchedFromVsCodeExtension();
384
+ push(checks, {
385
+ id: "vscode.settings",
386
+ label: "VS Code extension setting",
387
+ status: configured ? "ok" : launchedByExtension ? "skip" : "warn",
388
+ message: configured ? `brassAgent.command = ${configured}` : launchedByExtension ? `No workspace brassAgent.command needed; launched by the VS Code extension (${_nullishCoalesce(process.env.BRASS_AGENT_VSCODE_CLI_SOURCE, () => ( "auto"))}).` : "No workspace .vscode/settings.json brassAgent.command found."
389
+ });
390
+ }
391
+ if (repoRoot) {
392
+ const cliSource = _path.join.call(void 0, repoRoot, "src", "agent", "cli", "main.ts");
393
+ const cliBuild = _path.join.call(void 0, repoRoot, "dist", "agent", "cli", "main.cjs");
394
+ const extensionDir = _path.join.call(void 0, repoRoot, "extensions", "vscode-brass-agent");
395
+ push(checks, {
396
+ id: "repo.root",
397
+ label: "brass-runtime repo",
398
+ status: "ok",
399
+ message: repoRoot
400
+ });
401
+ push(checks, {
402
+ id: "repo.cliSource",
403
+ label: "CLI source",
404
+ status: _fs.existsSync.call(void 0, cliSource) ? "ok" : "fail",
405
+ message: _fs.existsSync.call(void 0, cliSource) ? `Found ${cliSource}` : `Missing ${cliSource}`
406
+ });
407
+ push(checks, {
408
+ id: "repo.cliBuild",
409
+ label: "CLI build",
410
+ status: _fs.existsSync.call(void 0, cliBuild) ? "ok" : "warn",
411
+ message: _fs.existsSync.call(void 0, cliBuild) ? `Found ${cliBuild}` : "dist/agent/cli/main.cjs is missing; run npm run build."
412
+ });
413
+ push(checks, {
414
+ id: "repo.extensionDir",
415
+ label: "VS Code extension source",
416
+ status: _fs.existsSync.call(void 0, _path.join.call(void 0, extensionDir, "package.json")) ? "ok" : "warn",
417
+ message: _fs.existsSync.call(void 0, _path.join.call(void 0, extensionDir, "package.json")) ? extensionDir : "extensions/vscode-brass-agent was not found."
418
+ });
419
+ push(checks, {
420
+ id: "repo.extensionBuild",
421
+ label: "VS Code extension build",
422
+ status: _fs.existsSync.call(void 0, _path.join.call(void 0, extensionDir, "out", "extension.js")) ? "ok" : "warn",
423
+ message: _fs.existsSync.call(void 0, _path.join.call(void 0, extensionDir, "out", "extension.js")) ? "out/extension.js exists." : "Extension output missing; run npm run agent:vscode:package or compile the extension."
424
+ });
425
+ const vsix = newestVsix(extensionDir);
426
+ push(checks, {
427
+ id: "repo.extensionVsix",
428
+ label: "VSIX package",
429
+ status: vsix ? "ok" : "warn",
430
+ message: vsix ? `Found ${vsix}` : "No .vsix found in extensions/vscode-brass-agent."
431
+ });
432
+ const cliReadable = await _promises.access.call(void 0, cliBuild, _promises.constants.R_OK).then(() => true).catch(() => false);
433
+ push(checks, {
434
+ id: "repo.cliReadable",
435
+ label: "CLI artifact readable",
436
+ status: cliReadable ? "ok" : "warn",
437
+ message: cliReadable ? "Built CLI artifact is readable." : "Built CLI artifact is not readable yet."
438
+ });
439
+ } else {
440
+ push(checks, {
441
+ id: "repo.root",
442
+ label: "brass-runtime repo",
443
+ status: "skip",
444
+ message: "Not running inside a brass-runtime checkout; local source/build checks skipped."
445
+ });
446
+ }
447
+ const status = checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "ok";
448
+ return {
449
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
450
+ cwd,
451
+ ...options.configPath ? { configPath: options.configPath } : {},
452
+ ...repoRoot ? { repoRoot } : {},
453
+ status,
454
+ checks
455
+ };
456
+ };
457
+ var icon = (status) => {
458
+ switch (status) {
459
+ case "ok":
460
+ return "\u2713";
461
+ case "warn":
462
+ return "!";
463
+ case "fail":
464
+ return "\u2717";
465
+ case "skip":
466
+ return "-";
467
+ }
468
+ };
469
+ var printAgentDoctorReport = (report) => {
470
+ console.log("brass-agent doctor");
471
+ console.log(`workspace: ${report.cwd}`);
472
+ if (report.configPath) console.log(`config: ${report.configPath}`);
473
+ if (report.repoRoot) console.log(`repo: ${report.repoRoot}`);
474
+ console.log(`status: ${report.status}`);
475
+ console.log("");
476
+ for (const check of report.checks) {
477
+ console.log(`${icon(check.status)} ${check.label}: ${check.message}`);
478
+ }
479
+ };
480
+
481
+ // src/agent/cli/envFile.ts
482
+
483
+
484
+ var DEFAULT_AGENT_ENV_FILE_NAMES = [".brass-agent.env", ".env.local", ".env"];
485
+ var BASE_ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set([
486
+ "BRASS_LLM_PROVIDER",
487
+ "BRASS_FAKE_LLM_RESPONSE",
488
+ "BRASS_GOOGLE_API_KEY",
489
+ "GOOGLE_API_KEY",
490
+ "GEMINI_API_KEY",
491
+ "BRASS_GOOGLE_MODEL",
492
+ "BRASS_GOOGLE_API_VERSION",
493
+ "BRASS_GOOGLE_BASE_URL",
494
+ "BRASS_GOOGLE_ENDPOINT",
495
+ "BRASS_GOOGLE_SYSTEM_INSTRUCTION",
496
+ "BRASS_GOOGLE_TEMPERATURE",
497
+ "BRASS_GOOGLE_TOP_P",
498
+ "BRASS_GOOGLE_TOP_K",
499
+ "BRASS_GOOGLE_MAX_OUTPUT_TOKENS",
500
+ "BRASS_LLM_ENDPOINT",
501
+ "BRASS_LLM_API_KEY",
502
+ "BRASS_LLM_MODEL",
503
+ "BRASS_AGENT_APPROVAL",
504
+ "BRASS_AGENT_AUTO_APPROVE",
505
+ "BRASS_CODE_CMD"
506
+ ]);
507
+ var unique = (values) => Array.from(new Set(values));
508
+ var parseQuotedValue = (value) => {
509
+ if (value.length < 2) return void 0;
510
+ const quote = value[0];
511
+ if (quote !== '"' && quote !== "'" || value[value.length - 1] !== quote) return void 0;
512
+ const inner = value.slice(1, -1);
513
+ if (quote === "'") return inner;
514
+ return inner.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
515
+ };
516
+ var parseEnvLine = (line) => {
517
+ const trimmed = line.trim();
518
+ if (!trimmed || trimmed.startsWith("#")) return { type: "skip" };
519
+ const withoutExport = trimmed.startsWith("export ") ? trimmed.slice("export ".length).trimStart() : trimmed;
520
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(withoutExport);
521
+ if (!match) return { type: "invalid" };
522
+ const key = _nullishCoalesce(match[1], () => ( ""));
523
+ const rawValue = _nullishCoalesce(match[2], () => ( ""));
524
+ const compact = rawValue.trim();
525
+ const quoted = parseQuotedValue(compact);
526
+ const value = _nullishCoalesce(quoted, () => ( compact.replace(/\s+#.*$/u, "").trim()));
527
+ return { type: "assignment", key, value };
528
+ };
529
+ var candidatePaths = (cwd, envFile) => {
530
+ if (envFile) return [_path.isAbsolute.call(void 0, envFile) ? envFile : _path.resolve.call(void 0, cwd, envFile)];
531
+ return DEFAULT_AGENT_ENV_FILE_NAMES.map((name) => _path.join.call(void 0, cwd, name));
532
+ };
533
+ var loadAgentEnvFile = (options) => {
534
+ const cwd = _path.resolve.call(void 0, options.cwd);
535
+ const extraKeys = _nullishCoalesce(_optionalChain([options, 'access', _25 => _25.allowedExtraKeys, 'optionalAccess', _26 => _26.filter, 'call', _27 => _27(Boolean)]), () => ( []));
536
+ const allowedKeys = /* @__PURE__ */ new Set([...BASE_ALLOWED_ENV_KEYS, ...extraKeys]);
537
+ const files = options.noEnvFile ? [] : candidatePaths(cwd, options.envFile);
538
+ const paths = [];
539
+ const loadedKeys = [];
540
+ const alreadySetKeys = [];
541
+ const emptyKeys = [];
542
+ const ignoredKeys = [];
543
+ const invalidLines = [];
544
+ const errors = [];
545
+ if (options.noEnvFile) {
546
+ return {
547
+ cwd,
548
+ disabled: true,
549
+ filesChecked: [],
550
+ paths: [],
551
+ loadedKeys: [],
552
+ alreadySetKeys: [],
553
+ emptyKeys: [],
554
+ ignoredKeys: [],
555
+ invalidLines: [],
556
+ errors: []
557
+ };
558
+ }
559
+ for (const path of files) {
560
+ if (!_fs.existsSync.call(void 0, path)) {
561
+ if (options.envFile) errors.push(`Env file does not exist: ${path}`);
562
+ continue;
563
+ }
564
+ paths.push(path);
565
+ let raw;
566
+ try {
567
+ raw = _fs.readFileSync.call(void 0, path, "utf8").replace(/^\uFEFF/u, "");
568
+ } catch (error) {
569
+ errors.push(`Could not read env file ${path}: ${error instanceof Error ? error.message : String(error)}`);
570
+ continue;
571
+ }
572
+ for (const [index, line] of raw.split(/\r?\n/gu).entries()) {
573
+ const parsed = parseEnvLine(line);
574
+ if (parsed.type === "skip") continue;
575
+ if (parsed.type === "invalid") {
576
+ invalidLines.push(`${path}:${index + 1}`);
577
+ continue;
578
+ }
579
+ if (!allowedKeys.has(parsed.key)) {
580
+ ignoredKeys.push(parsed.key);
581
+ continue;
582
+ }
583
+ if (!parsed.value) {
584
+ emptyKeys.push(parsed.key);
585
+ continue;
586
+ }
587
+ if (process.env[parsed.key] !== void 0) {
588
+ alreadySetKeys.push(parsed.key);
589
+ continue;
590
+ }
591
+ process.env[parsed.key] = parsed.value;
592
+ loadedKeys.push(parsed.key);
593
+ }
594
+ }
595
+ return {
596
+ cwd,
597
+ disabled: false,
598
+ ...options.envFile ? { explicitPath: _path.isAbsolute.call(void 0, options.envFile) ? options.envFile : _path.resolve.call(void 0, cwd, options.envFile) } : {},
599
+ filesChecked: files,
600
+ paths,
601
+ loadedKeys: unique(loadedKeys),
602
+ alreadySetKeys: unique(alreadySetKeys),
603
+ emptyKeys: unique(emptyKeys),
604
+ ignoredKeys: unique(ignoredKeys),
605
+ invalidLines: unique(invalidLines),
606
+ errors: unique(errors)
607
+ };
608
+ };
609
+
610
+ // src/agent/cli/init.ts
611
+
612
+
613
+
614
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
615
+ var readJsonFile2 = (path) => {
616
+ try {
617
+ return JSON.parse(_fs.readFileSync.call(void 0, path, "utf8"));
618
+ } catch (e3) {
619
+ return void 0;
620
+ }
621
+ };
622
+ var readPackageJson = (cwd) => {
623
+ const json = readJsonFile2(_path.join.call(void 0, cwd, "package.json"));
624
+ return isRecord2(json) ? json : void 0;
625
+ };
626
+ var scriptNames = (packageJson) => {
627
+ const scripts = _optionalChain([packageJson, 'optionalAccess', _28 => _28.scripts]);
628
+ if (!isRecord2(scripts)) return [];
629
+ return Object.entries(scripts).filter(([, value]) => typeof value === "string").map(([name]) => name).sort();
630
+ };
631
+ var hasAnyScript = (scripts, candidates) => candidates.some((candidate) => scripts.includes(candidate));
632
+ var inferPackageManager = (cwd, packageJson) => {
633
+ const packageManager = typeof _optionalChain([packageJson, 'optionalAccess', _29 => _29.packageManager]) === "string" ? packageJson.packageManager.split("@")[0] : void 0;
634
+ if (packageManager === "npm" || packageManager === "pnpm" || packageManager === "yarn" || packageManager === "bun") {
635
+ return packageManager;
636
+ }
637
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "pnpm-lock.yaml"))) return "pnpm";
638
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "yarn.lock"))) return "yarn";
639
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "bun.lockb")) || _fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "bun.lock"))) return "bun";
640
+ if (_fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "package-lock.json")) || _fs.existsSync.call(void 0, _path.join.call(void 0, cwd, "npm-shrinkwrap.json"))) return "npm";
641
+ return "auto";
642
+ };
643
+ var llmConfigForProfile = (profile) => {
644
+ switch (profile) {
645
+ case "default":
646
+ return void 0;
647
+ case "google":
648
+ return {
649
+ provider: "google",
650
+ model: "gemini-2.5-flash",
651
+ apiKeyEnv: "GEMINI_API_KEY",
652
+ temperature: 0.2,
653
+ maxOutputTokens: 4096
654
+ };
655
+ case "openai-compatible":
656
+ return {
657
+ provider: "openai-compatible",
658
+ endpoint: "https://api.openai.com/v1/chat/completions",
659
+ model: "gpt-4.1",
660
+ apiKeyEnv: "BRASS_LLM_API_KEY",
661
+ temperature: 0.2
662
+ };
663
+ case "fake":
664
+ return {
665
+ provider: "fake",
666
+ fakeResponse: "Fake plan from brass-agent init. Configure a real provider when ready."
667
+ };
668
+ }
669
+ };
670
+ var buildAgentConfig = (cwd, profile) => {
671
+ const packageJson = readPackageJson(cwd);
672
+ const scripts = scriptNames(packageJson);
673
+ const includeTypecheck = hasAnyScript(scripts, ["typecheck", "type-check", "check-types", "tsc", "check"]);
674
+ const includeLint = hasAnyScript(scripts, ["lint", "lint:ci"]);
675
+ const llm = llmConfigForProfile(profile);
676
+ return {
677
+ mode: "propose",
678
+ approval: "auto",
679
+ ...llm ? { llm } : {},
680
+ project: {
681
+ packageManager: inferPackageManager(cwd, packageJson),
682
+ testScriptNames: ["test", "test:ci", "test:unit"],
683
+ includeTypecheck,
684
+ includeLint,
685
+ maxValidationCommands: 2
686
+ },
687
+ context: {
688
+ enabled: true,
689
+ maxSearchQueries: 3,
690
+ maxFiles: 4,
691
+ maxSearchResults: 40,
692
+ globs: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mjs", "*.cjs", "*.json", "*.md", "*.yml", "*.yaml"],
693
+ excludeGlobs: [
694
+ ".env*",
695
+ "**/.env*",
696
+ "**/node_modules/**",
697
+ "**/dist/**",
698
+ "**/build/**",
699
+ "**/.git/**",
700
+ "**/*.pem",
701
+ "**/*.key",
702
+ "**/secrets/**"
703
+ ]
704
+ },
705
+ patchQuality: {
706
+ enabled: true,
707
+ maxRepairAttempts: 1
708
+ },
709
+ rollback: {
710
+ enabled: true,
711
+ onFinalValidationFailure: true,
712
+ strategy: "all",
713
+ maxRollbackDepth: 8,
714
+ runValidationAfterRollback: true,
715
+ allowForSuppliedPatches: false
716
+ },
717
+ redaction: {
718
+ enabled: true,
719
+ additionalPatterns: []
720
+ },
721
+ language: {
722
+ response: "auto"
723
+ },
724
+ permissions: {
725
+ shell: {
726
+ inheritDefaults: true,
727
+ ask: [
728
+ {
729
+ pattern: "npm run build",
730
+ reason: "Build commands can be slow and may produce large outputs.",
731
+ risk: "medium",
732
+ defaultAnswer: "approve"
733
+ },
734
+ {
735
+ pattern: "pnpm run build",
736
+ reason: "Build commands can be slow and may produce large outputs.",
737
+ risk: "medium",
738
+ defaultAnswer: "approve"
739
+ },
740
+ {
741
+ pattern: "yarn run build",
742
+ reason: "Build commands can be slow and may produce large outputs.",
743
+ risk: "medium",
744
+ defaultAnswer: "approve"
745
+ },
746
+ {
747
+ pattern: "bun run build",
748
+ reason: "Build commands can be slow and may produce large outputs.",
749
+ risk: "medium",
750
+ defaultAnswer: "approve"
751
+ }
752
+ ],
753
+ deny: [
754
+ "rm *",
755
+ "git push *",
756
+ "git reset *",
757
+ "git clean *"
758
+ ]
759
+ },
760
+ patchApply: {
761
+ decision: "ask",
762
+ reason: "Apply the generated unified diff to the workspace.",
763
+ risk: "high",
764
+ defaultAnswer: "reject"
765
+ }
766
+ },
767
+ tools: {
768
+ "fs.readFile": { timeoutMs: 1e4, retries: 1 },
769
+ "fs.exists": { timeoutMs: 5e3, retries: 0 },
770
+ "fs.searchText": { timeoutMs: 1e4, retries: 1 },
771
+ "shell.exec": { timeoutMs: 18e4, retries: 0 },
772
+ "llm.complete": { timeoutMs: 9e4, retries: 2 },
773
+ "patch.apply": { timeoutMs: 3e4, retries: 0 },
774
+ "patch.rollback": { timeoutMs: 3e4, retries: 0 }
775
+ }
776
+ };
777
+ };
778
+ var batchGoalsForWorkspace = (cwd) => {
779
+ const scripts = scriptNames(readPackageJson(cwd));
780
+ const goals = [{ preset: "inspect", mode: "read-only" }];
781
+ if (hasAnyScript(scripts, ["typecheck", "type-check", "check-types", "tsc", "check"])) {
782
+ goals.push({ preset: "typecheck", mode: "propose" });
783
+ }
784
+ if (hasAnyScript(scripts, ["lint", "lint:ci"])) {
785
+ goals.push({ preset: "lint", mode: "propose" });
786
+ }
787
+ if (hasAnyScript(scripts, ["test", "test:ci", "test:unit"])) {
788
+ goals.push({ preset: "fix-tests", mode: "propose" });
789
+ }
790
+ return goals;
791
+ };
792
+ var buildBatchFile = (cwd) => `${JSON.stringify({
793
+ stopOnFailure: false,
794
+ goals: batchGoalsForWorkspace(cwd)
795
+ }, null, 2)}
796
+ `;
797
+ var buildEnvExample = (profile) => {
798
+ const lines = [
799
+ "# Brass Agent environment variables",
800
+ "# Copy this file to .env or .brass-agent.env, or export the variables in your shell.",
801
+ "# brass-agent auto-loads supported agent env keys from --cwd.",
802
+ "# Do not commit real API keys.",
803
+ ""
804
+ ];
805
+ if (profile === "fake") {
806
+ lines.push(
807
+ "BRASS_LLM_PROVIDER=fake",
808
+ "BRASS_FAKE_LLM_RESPONSE=Fake plan from .env.example",
809
+ ""
810
+ );
811
+ }
812
+ lines.push(
813
+ "# Google / Gemini",
814
+ "# Used when .brass-agent.json selects provider google, or when auto-detected.",
815
+ "GEMINI_API_KEY=",
816
+ "BRASS_GOOGLE_MODEL=gemini-2.5-flash",
817
+ "",
818
+ "# OpenAI-compatible providers",
819
+ "# BRASS_LLM_PROVIDER=openai-compatible",
820
+ "BRASS_LLM_ENDPOINT=https://api.openai.com/v1/chat/completions",
821
+ "BRASS_LLM_API_KEY=",
822
+ "BRASS_LLM_MODEL=gpt-4.1",
823
+ "",
824
+ "# Approval behavior: auto | interactive | approve | deny",
825
+ "# BRASS_AGENT_APPROVAL=auto",
826
+ ""
827
+ );
828
+ if (profile === "google") {
829
+ lines.splice(4, 0, "BRASS_LLM_PROVIDER=google", "");
830
+ } else if (profile === "openai-compatible") {
831
+ lines.splice(4, 0, "BRASS_LLM_PROVIDER=openai-compatible", "");
832
+ }
833
+ return `${lines.join("\n")}
834
+ `;
835
+ };
836
+ var buildAgentReadme = () => [
837
+ "# Brass Agent",
838
+ "",
839
+ "This workspace was initialized with `brass-agent --init`.",
840
+ "",
841
+ "Generated files:",
842
+ "",
843
+ "- `.brass-agent.json` \u2014 local policy/config for Brass Agent.",
844
+ "- `brass-agent.batch.json` \u2014 sample multi-goal batch workflow.",
845
+ "- `.env.example` \u2014 example environment variables. Copy to `.env` or `.brass-agent.env`; keep real secrets out of git.",
846
+ "",
847
+ "Recommended first commands:",
848
+ "",
849
+ "```bash",
850
+ "brass-agent --doctor",
851
+ "brass-agent --preset inspect",
852
+ "brass-agent --batch-file brass-agent.batch.json",
853
+ "```",
854
+ "",
855
+ "Apply mode is intentionally approval-gated:",
856
+ "",
857
+ "```bash",
858
+ 'brass-agent --apply "fix the failing tests"',
859
+ "```",
860
+ ""
861
+ ].join("\n");
862
+ var nextStepsForProfile = (profile) => {
863
+ const steps = [
864
+ "Review .brass-agent.json and adjust permissions/context budgets for this repo.",
865
+ "Run: brass-agent --doctor",
866
+ "Run: brass-agent --preset inspect",
867
+ "Run: brass-agent --batch-file brass-agent.batch.json"
868
+ ];
869
+ if (profile === "google") {
870
+ return ["Set GEMINI_API_KEY in your shell, `.env`, or `.brass-agent.env`.", ...steps];
871
+ }
872
+ if (profile === "openai-compatible") {
873
+ return ["Set BRASS_LLM_API_KEY and BRASS_LLM_ENDPOINT in your shell, `.env`, or `.brass-agent.env`.", ...steps];
874
+ }
875
+ if (profile === "default") {
876
+ return ["Set an LLM provider env var in your shell, `.env`, or `.brass-agent.env` when ready, or let the CLI fall back to fake mode.", ...steps];
877
+ }
878
+ return steps;
879
+ };
880
+ var writeInitFile = _chunkTGOMLZ65js.async.call(void 0, options) => {
881
+ const path = _path.resolve.call(void 0, options.cwd, options.relativePath);
882
+ const exists = _fs.existsSync.call(void 0, path);
883
+ const status = exists ? options.force ? "overwritten" : "skipped" : "created";
884
+ if (!options.dryRun && status !== "skipped") {
885
+ await _promises.mkdir.call(void 0, _path.dirname.call(void 0, path), { recursive: true });
886
+ await _promises.writeFile.call(void 0, path, options.content, "utf8");
887
+ }
888
+ return {
889
+ path,
890
+ relativePath: options.relativePath,
891
+ status,
892
+ bytes: Buffer.byteLength(options.content, "utf8")
893
+ };
894
+ };
895
+ var initializeAgentWorkspace = _chunkTGOMLZ65js.async.call(void 0, options) => {
896
+ const cwd = _path.resolve.call(void 0, options.cwd);
897
+ const profile = _nullishCoalesce(options.profile, () => ( "default"));
898
+ const force = _nullishCoalesce(options.force, () => ( false));
899
+ const dryRun = _nullishCoalesce(options.dryRun, () => ( false));
900
+ const config = buildAgentConfig(cwd, profile);
901
+ const files = await Promise.all([
902
+ writeInitFile({
903
+ cwd,
904
+ relativePath: ".brass-agent.json",
905
+ content: `${JSON.stringify(config, null, 2)}
906
+ `,
907
+ force,
908
+ dryRun
909
+ }),
910
+ writeInitFile({
911
+ cwd,
912
+ relativePath: "brass-agent.batch.json",
913
+ content: buildBatchFile(cwd),
914
+ force,
915
+ dryRun
916
+ }),
917
+ writeInitFile({
918
+ cwd,
919
+ relativePath: ".env.example",
920
+ content: buildEnvExample(profile),
921
+ force,
922
+ dryRun
923
+ }),
924
+ writeInitFile({
925
+ cwd,
926
+ relativePath: "BRASS_AGENT.md",
927
+ content: buildAgentReadme(),
928
+ force,
929
+ dryRun
930
+ })
931
+ ]);
932
+ return {
933
+ cwd,
934
+ profile,
935
+ dryRun,
936
+ files,
937
+ nextSteps: nextStepsForProfile(profile)
938
+ };
939
+ };
940
+ var statusIcon = (status) => {
941
+ switch (status) {
942
+ case "created":
943
+ return "\u2713";
944
+ case "overwritten":
945
+ return "!";
946
+ case "skipped":
947
+ return "-";
948
+ }
949
+ };
950
+ var printAgentInitResult = (result) => {
951
+ console.log(`brass-agent init${result.dryRun ? " (dry run)" : ""}`);
952
+ console.log(`workspace: ${result.cwd}`);
953
+ console.log(`profile: ${result.profile}`);
954
+ console.log("");
955
+ for (const file of result.files) {
956
+ console.log(`${statusIcon(file.status)} ${file.status} ${file.relativePath}`);
957
+ }
958
+ console.log("");
959
+ console.log("next steps:");
960
+ for (const step of result.nextSteps) {
961
+ console.log(`- ${step}`);
962
+ }
963
+ };
964
+
965
+ // src/agent/cli/main.ts
966
+ var dynamicImport2 = new Function("specifier", "return import(specifier)");
967
+ var readPatchFile = _chunkTGOMLZ65js.async.call(void 0, cwd, patchFile) => {
968
+ const nodePath = await dynamicImport2("node:path");
969
+ const nodeFs = await dynamicImport2("node:fs/promises");
970
+ return nodeFs.readFile(nodePath.resolve(cwd, patchFile), "utf8");
971
+ };
972
+ var parseOptionalNumber = (value) => {
973
+ if (value === void 0 || value.trim() === "") return void 0;
974
+ const parsed = Number(value);
975
+ return Number.isFinite(parsed) ? parsed : void 0;
976
+ };
977
+ var isAgentMode = (value) => value === "read-only" || value === "propose" || value === "write" || value === "autonomous";
978
+ var isCliApprovalMode = (value) => value === "auto" || value === "interactive" || value === "approve" || value === "deny";
979
+ var isAgentInitProfile = (value) => value === "default" || value === "google" || value === "openai-compatible" || value === "fake";
980
+ var isAgentResponseLanguage = (value) => ["auto", "match-user", "en", "es", "pt", "fr", "de", "it", "custom"].includes(value);
981
+ var readFlagValue = (argv, index, flag) => {
982
+ const current = argv[index];
983
+ const inlineValue = current.startsWith(`${flag}=`) ? current.slice(flag.length + 1) : void 0;
984
+ if (inlineValue !== void 0 && inlineValue !== "") return [inlineValue, index];
985
+ const next = argv[index + 1];
986
+ if (!next) throw new Error(`${flag} requires a value`);
987
+ return [next, index + 1];
988
+ };
989
+ var parseCliArgs = (argv) => {
990
+ let cwd = process.cwd();
991
+ let discoverWorkspace = true;
992
+ let where = false;
993
+ let mode = "propose";
994
+ let modeSpecified = false;
995
+ let showHelp = false;
996
+ let output = "human";
997
+ let approval = "auto";
998
+ let approvalSpecified = false;
999
+ let configPath;
1000
+ let noConfig = false;
1001
+ let envFile;
1002
+ let noEnvFile = false;
1003
+ let protocolFullPatches = false;
1004
+ let patchFile;
1005
+ let patchFileMode = "apply";
1006
+ let saveRunDir;
1007
+ let ci = false;
1008
+ let failOnPatchProposed = false;
1009
+ let preset;
1010
+ let batchFile;
1011
+ let batchStopOnFailure;
1012
+ let doctor = false;
1013
+ let init = false;
1014
+ let initForce = false;
1015
+ let initProfile = "default";
1016
+ let initDryRun = false;
1017
+ let language;
1018
+ const goalParts = [];
1019
+ for (let index = 0; index < argv.length; index += 1) {
1020
+ const arg = argv[index];
1021
+ if (arg === "--") {
1022
+ goalParts.push(...argv.slice(index + 1));
1023
+ break;
1024
+ }
1025
+ if (arg === "--help" || arg === "-h") {
1026
+ showHelp = true;
1027
+ continue;
1028
+ }
1029
+ if (arg === "--doctor") {
1030
+ doctor = true;
1031
+ continue;
1032
+ }
1033
+ if (arg === "--where" || arg === "--print-workspace") {
1034
+ where = true;
1035
+ continue;
1036
+ }
1037
+ if (arg === "--no-discover-workspace") {
1038
+ discoverWorkspace = false;
1039
+ continue;
1040
+ }
1041
+ if (arg === "--init") {
1042
+ init = true;
1043
+ continue;
1044
+ }
1045
+ if (arg === "--force" || arg === "--init-force") {
1046
+ initForce = true;
1047
+ continue;
1048
+ }
1049
+ if (arg === "--init-dry-run") {
1050
+ initDryRun = true;
1051
+ continue;
1052
+ }
1053
+ if (arg === "--init-profile" || arg.startsWith("--init-profile=")) {
1054
+ const [value, nextIndex] = readFlagValue(argv, index, "--init-profile");
1055
+ if (!isAgentInitProfile(value)) {
1056
+ throw new Error("--init-profile requires one of: default, google, openai-compatible, fake");
1057
+ }
1058
+ initProfile = value;
1059
+ index = nextIndex;
1060
+ continue;
1061
+ }
1062
+ if (arg === "--init-provider" || arg.startsWith("--init-provider=")) {
1063
+ const [value, nextIndex] = readFlagValue(argv, index, "--init-provider");
1064
+ if (value === "auto") {
1065
+ initProfile = "default";
1066
+ } else if (isAgentInitProfile(value)) {
1067
+ initProfile = value;
1068
+ } else {
1069
+ throw new Error("--init-provider requires one of: auto, fake, google, openai-compatible");
1070
+ }
1071
+ index = nextIndex;
1072
+ continue;
1073
+ }
1074
+ if (arg === "--language" || arg.startsWith("--language=")) {
1075
+ const [value, nextIndex] = readFlagValue(argv, index, "--language");
1076
+ if (!isAgentResponseLanguage(value)) {
1077
+ throw new Error("--language requires one of: auto, match-user, en, es, pt, fr, de, it, custom");
1078
+ }
1079
+ language = value;
1080
+ index = nextIndex;
1081
+ continue;
1082
+ }
1083
+ if (arg === "--json") {
1084
+ output = "json";
1085
+ continue;
1086
+ }
1087
+ if (arg === "--events-json") {
1088
+ output = "events-json";
1089
+ continue;
1090
+ }
1091
+ if (arg === "--protocol-json") {
1092
+ output = "protocol-json";
1093
+ continue;
1094
+ }
1095
+ if (arg === "--protocol-full-patches") {
1096
+ protocolFullPatches = true;
1097
+ continue;
1098
+ }
1099
+ if (arg === "--preset" || arg.startsWith("--preset=")) {
1100
+ const [value, nextIndex] = readFlagValue(argv, index, "--preset");
1101
+ if (!_chunkHRVX2IYWjs.isAgentPreset.call(void 0, value)) {
1102
+ throw new Error("--preset requires one of: fix-tests, inspect, typecheck, lint");
1103
+ }
1104
+ preset = value;
1105
+ index = nextIndex;
1106
+ continue;
1107
+ }
1108
+ if (arg === "--batch-file" || arg.startsWith("--batch-file=")) {
1109
+ const [value, nextIndex] = readFlagValue(argv, index, "--batch-file");
1110
+ batchFile = value;
1111
+ index = nextIndex;
1112
+ continue;
1113
+ }
1114
+ if (arg === "--batch-stop-on-failure") {
1115
+ batchStopOnFailure = true;
1116
+ continue;
1117
+ }
1118
+ if (arg === "--batch-continue-on-failure") {
1119
+ batchStopOnFailure = false;
1120
+ continue;
1121
+ }
1122
+ if (arg === "--ci") {
1123
+ ci = true;
1124
+ continue;
1125
+ }
1126
+ if (arg === "--fail-on-patch-proposed") {
1127
+ failOnPatchProposed = true;
1128
+ continue;
1129
+ }
1130
+ if (arg === "--yes" || arg === "-y") {
1131
+ approval = "approve";
1132
+ approvalSpecified = true;
1133
+ continue;
1134
+ }
1135
+ if (arg === "--no-input") {
1136
+ approval = "deny";
1137
+ approvalSpecified = true;
1138
+ continue;
1139
+ }
1140
+ if (arg === "--approval" || arg.startsWith("--approval=")) {
1141
+ const [value, nextIndex] = readFlagValue(argv, index, "--approval");
1142
+ if (!isCliApprovalMode(value)) {
1143
+ throw new Error("--approval requires one of: auto, interactive, approve, deny");
1144
+ }
1145
+ approval = value;
1146
+ approvalSpecified = true;
1147
+ index = nextIndex;
1148
+ continue;
1149
+ }
1150
+ if (arg === "--apply") {
1151
+ mode = "write";
1152
+ modeSpecified = true;
1153
+ continue;
1154
+ }
1155
+ if (arg === "--patch-file" || arg.startsWith("--patch-file=")) {
1156
+ const [value, nextIndex] = readFlagValue(argv, index, "--patch-file");
1157
+ patchFile = value;
1158
+ index = nextIndex;
1159
+ continue;
1160
+ }
1161
+ if (arg === "--apply-patch-file" || arg.startsWith("--apply-patch-file=")) {
1162
+ const [value, nextIndex] = readFlagValue(argv, index, "--apply-patch-file");
1163
+ patchFile = value;
1164
+ patchFileMode = "apply";
1165
+ mode = "write";
1166
+ modeSpecified = true;
1167
+ index = nextIndex;
1168
+ continue;
1169
+ }
1170
+ if (arg === "--rollback-patch-file" || arg.startsWith("--rollback-patch-file=")) {
1171
+ const [value, nextIndex] = readFlagValue(argv, index, "--rollback-patch-file");
1172
+ patchFile = value;
1173
+ patchFileMode = "rollback";
1174
+ mode = "write";
1175
+ modeSpecified = true;
1176
+ index = nextIndex;
1177
+ continue;
1178
+ }
1179
+ if (arg === "--mode" || arg.startsWith("--mode=")) {
1180
+ const [value, nextIndex] = readFlagValue(argv, index, "--mode");
1181
+ if (!isAgentMode(value)) {
1182
+ throw new Error("--mode requires one of: read-only, propose, write, autonomous");
1183
+ }
1184
+ mode = value;
1185
+ modeSpecified = true;
1186
+ index = nextIndex;
1187
+ continue;
1188
+ }
1189
+ if (arg === "--cwd" || arg.startsWith("--cwd=")) {
1190
+ const [value, nextIndex] = readFlagValue(argv, index, "--cwd");
1191
+ cwd = value;
1192
+ index = nextIndex;
1193
+ continue;
1194
+ }
1195
+ if (arg === "--save-run" || arg.startsWith("--save-run=")) {
1196
+ const [value, nextIndex] = readFlagValue(argv, index, "--save-run");
1197
+ saveRunDir = value;
1198
+ index = nextIndex;
1199
+ continue;
1200
+ }
1201
+ if (arg === "--config" || arg.startsWith("--config=")) {
1202
+ const [value, nextIndex] = readFlagValue(argv, index, "--config");
1203
+ configPath = value;
1204
+ noConfig = false;
1205
+ index = nextIndex;
1206
+ continue;
1207
+ }
1208
+ if (arg === "--no-config") {
1209
+ configPath = void 0;
1210
+ noConfig = true;
1211
+ continue;
1212
+ }
1213
+ if (arg === "--env-file" || arg.startsWith("--env-file=")) {
1214
+ const [value, nextIndex] = readFlagValue(argv, index, "--env-file");
1215
+ envFile = value;
1216
+ noEnvFile = false;
1217
+ index = nextIndex;
1218
+ continue;
1219
+ }
1220
+ if (arg === "--no-env-file") {
1221
+ envFile = void 0;
1222
+ noEnvFile = true;
1223
+ continue;
1224
+ }
1225
+ if (arg.startsWith("--")) {
1226
+ throw new Error(`Unknown option: ${arg}`);
1227
+ }
1228
+ goalParts.push(arg);
1229
+ }
1230
+ return {
1231
+ cwd,
1232
+ discoverWorkspace,
1233
+ where,
1234
+ goalText: goalParts.join(" ").trim(),
1235
+ mode,
1236
+ modeSpecified,
1237
+ showHelp,
1238
+ output,
1239
+ approval,
1240
+ approvalSpecified,
1241
+ ...configPath ? { configPath } : {},
1242
+ noConfig,
1243
+ ...envFile ? { envFile } : {},
1244
+ noEnvFile,
1245
+ protocolFullPatches,
1246
+ patchFileMode,
1247
+ ci,
1248
+ failOnPatchProposed,
1249
+ ...preset ? { preset } : {},
1250
+ ...batchFile ? { batchFile } : {},
1251
+ ...batchStopOnFailure !== void 0 ? { batchStopOnFailure } : {},
1252
+ doctor,
1253
+ init,
1254
+ initForce,
1255
+ initProfile,
1256
+ initDryRun,
1257
+ ...language ? { language } : {},
1258
+ ...saveRunDir ? { saveRunDir } : {},
1259
+ ...patchFile ? { patchFile } : {}
1260
+ };
1261
+ };
1262
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1263
+ var parseBatchGoal = (value, path) => {
1264
+ if (typeof value === "string") return value;
1265
+ if (!isRecord3(value)) throw new Error(`${path} must be a string or object.`);
1266
+ const goal = value.goal;
1267
+ const preset = value.preset;
1268
+ const mode = value.mode;
1269
+ const cwd = value.cwd;
1270
+ const patchFile = value.patchFile;
1271
+ const patchFileMode = value.patchFileMode;
1272
+ const saveRunDir = value.saveRunDir;
1273
+ if (goal !== void 0 && typeof goal !== "string") throw new Error(`${path}.goal must be a string.`);
1274
+ if (cwd !== void 0 && typeof cwd !== "string") throw new Error(`${path}.cwd must be a string.`);
1275
+ if (patchFile !== void 0 && typeof patchFile !== "string") throw new Error(`${path}.patchFile must be a string.`);
1276
+ if (saveRunDir !== void 0 && typeof saveRunDir !== "string") throw new Error(`${path}.saveRunDir must be a string.`);
1277
+ if (preset !== void 0 && (typeof preset !== "string" || !_chunkHRVX2IYWjs.isAgentPreset.call(void 0, preset))) {
1278
+ throw new Error(`${path}.preset must be one of: fix-tests, inspect, typecheck, lint.`);
1279
+ }
1280
+ if (mode !== void 0 && (typeof mode !== "string" || !isAgentMode(mode))) {
1281
+ throw new Error(`${path}.mode must be one of: read-only, propose, write, autonomous.`);
1282
+ }
1283
+ if (patchFileMode !== void 0 && patchFileMode !== "apply" && patchFileMode !== "rollback") {
1284
+ throw new Error(`${path}.patchFileMode must be apply or rollback.`);
1285
+ }
1286
+ if (goal === void 0 && preset === void 0 && patchFile === void 0) {
1287
+ throw new Error(`${path} must include goal, preset, or patchFile.`);
1288
+ }
1289
+ return {
1290
+ ...goal ? { goal } : {},
1291
+ ...preset ? { preset } : {},
1292
+ ...mode ? { mode } : {},
1293
+ ...cwd ? { cwd } : {},
1294
+ ...patchFile ? { patchFile } : {},
1295
+ ...patchFileMode ? { patchFileMode } : {},
1296
+ ...saveRunDir ? { saveRunDir } : {}
1297
+ };
1298
+ };
1299
+ var parseBatchGoalsJson = (value) => {
1300
+ const rawGoals = Array.isArray(value) ? value : isRecord3(value) && Array.isArray(value.goals) ? value.goals : void 0;
1301
+ if (!rawGoals) throw new Error("Batch file must be a JSON array or an object with a goals array.");
1302
+ return rawGoals.map((goal, index) => parseBatchGoal(goal, `goals[${index}]`));
1303
+ };
1304
+ var readBatchFile = _chunkTGOMLZ65js.async.call(void 0, cwd, batchFile) => {
1305
+ const nodePath = await dynamicImport2("node:path");
1306
+ const nodeFs = await dynamicImport2("node:fs/promises");
1307
+ const path = nodePath.isAbsolute(batchFile) ? batchFile : nodePath.resolve(cwd, batchFile);
1308
+ const raw = String(await nodeFs.readFile(path, "utf8")).replace(/^\uFEFF/, "");
1309
+ try {
1310
+ return parseBatchGoalsJson(JSON.parse(raw));
1311
+ } catch (error) {
1312
+ if (!(error instanceof SyntaxError)) throw error;
1313
+ const goals = raw.split(/\r?\n/g).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1314
+ if (goals.length === 0) throw new Error(`Batch file has no goals: ${path}`);
1315
+ return goals;
1316
+ }
1317
+ };
1318
+ var resolveBatchGoalText = (item, fallbackPatchFile) => {
1319
+ if (typeof item === "string") return item;
1320
+ if (item.goal) return item.goal;
1321
+ if (item.preset) return _chunkHRVX2IYWjs.goalForAgentPreset.call(void 0, item.preset);
1322
+ if (_nullishCoalesce(item.patchFile, () => ( fallbackPatchFile))) return "apply supplied patch";
1323
+ return "";
1324
+ };
1325
+ var resolveBatchMode = (item, parsed, config) => {
1326
+ if (typeof item !== "string" && item.mode) return item.mode;
1327
+ if (typeof item !== "string" && item.preset === "inspect" && !parsed.modeSpecified) return "read-only";
1328
+ return parsed.modeSpecified ? parsed.mode : _nullishCoalesce(config.mode, () => ( parsed.mode));
1329
+ };
1330
+ var resolveBatchRuns = (items, parsed, config) => items.map((item, index) => {
1331
+ const goalText = resolveBatchGoalText(item, parsed.patchFile);
1332
+ const cwd = typeof item === "string" ? parsed.cwd : _nullishCoalesce(item.cwd, () => ( parsed.cwd));
1333
+ const patchFile = typeof item === "string" ? parsed.patchFile : _nullishCoalesce(item.patchFile, () => ( parsed.patchFile));
1334
+ const patchFileMode = typeof item === "string" ? parsed.patchFileMode : _nullishCoalesce(item.patchFileMode, () => ( parsed.patchFileMode));
1335
+ const saveRunDir = typeof item === "string" ? parsed.saveRunDir : _nullishCoalesce(item.saveRunDir, () => ( parsed.saveRunDir));
1336
+ if (!goalText) throw new Error(`Batch goal ${index + 1} resolved to an empty goal.`);
1337
+ return {
1338
+ index,
1339
+ cwd,
1340
+ goalText,
1341
+ mode: resolveBatchMode(item, parsed, config),
1342
+ patchFileMode,
1343
+ ...patchFile ? { patchFile } : {},
1344
+ ...saveRunDir ? { saveRunDir } : {}
1345
+ };
1346
+ });
1347
+ var resolveParsedConfig = _chunkTGOMLZ65js.async.call(void 0, parsed) => {
1348
+ const workspaceDiscovery = _chunkHRVX2IYWjs.discoverNodeWorkspaceRoot.call(void 0, parsed.cwd, {
1349
+ enabled: parsed.discoverWorkspace
1350
+ });
1351
+ const cwdResolved = workspaceDiscovery.cwd;
1352
+ const parsedAtWorkspace = {
1353
+ ...parsed,
1354
+ cwd: cwdResolved
1355
+ };
1356
+ const loaded = await _chunkHRVX2IYWjs.loadNodeAgentConfig.call(void 0, {
1357
+ cwd: cwdResolved,
1358
+ configPath: parsed.configPath,
1359
+ noConfig: parsed.noConfig
1360
+ });
1361
+ const envFileLoad = loadAgentEnvFile({
1362
+ cwd: cwdResolved,
1363
+ envFile: parsed.envFile,
1364
+ noEnvFile: parsed.noEnvFile,
1365
+ allowedExtraKeys: _optionalChain([loaded, 'access', _30 => _30.config, 'access', _31 => _31.llm, 'optionalAccess', _32 => _32.apiKeyEnv]) ? [loaded.config.llm.apiKeyEnv] : []
1366
+ });
1367
+ const shouldUseConfigBatch = !parsed.goalText && !parsed.preset && !parsed.patchFile;
1368
+ const batchItems = parsed.batchFile ? await readBatchFile(cwdResolved, parsed.batchFile) : shouldUseConfigBatch ? _nullishCoalesce(_optionalChain([loaded, 'access', _33 => _33.config, 'access', _34 => _34.batch, 'optionalAccess', _35 => _35.goals]), () => ( [])) : [];
1369
+ const batchRuns = resolveBatchRuns(batchItems, parsedAtWorkspace, loaded.config);
1370
+ return {
1371
+ ...parsedAtWorkspace,
1372
+ goalText: parsed.goalText || (parsed.preset ? _chunkHRVX2IYWjs.goalForAgentPreset.call(void 0, parsed.preset) : parsed.patchFile ? "apply supplied patch" : parsed.goalText),
1373
+ mode: parsed.modeSpecified ? parsed.mode : parsed.preset === "inspect" ? "read-only" : _nullishCoalesce(loaded.config.mode, () => ( parsed.mode)),
1374
+ approval: parsed.approvalSpecified ? parsed.approval : _nullishCoalesce(loaded.config.approval, () => ( parsed.approval)),
1375
+ config: loaded.config,
1376
+ workspaceDiscovery,
1377
+ batchRuns,
1378
+ batchStopOnFailureResolved: _nullishCoalesce(_nullishCoalesce(parsed.batchStopOnFailure, () => ( _optionalChain([loaded, 'access', _36 => _36.config, 'access', _37 => _37.batch, 'optionalAccess', _38 => _38.stopOnFailure]))), () => ( parsed.ci)),
1379
+ envFileLoad,
1380
+ ...loaded.path ? { resolvedConfigPath: loaded.path } : {}
1381
+ };
1382
+ };
1383
+ var printHelp = () => {
1384
+ console.log([
1385
+ 'Usage: brass-agent [options] "goal"',
1386
+ "",
1387
+ "Options:",
1388
+ " --mode read-only|propose|write|autonomous",
1389
+ " Agent permission mode. Default: propose, or config.mode if present.",
1390
+ " --preset fix-tests|inspect|typecheck|lint",
1391
+ " Use a built-in goal preset when no explicit goal text is provided.",
1392
+ " --apply",
1393
+ " Alias for --mode write.",
1394
+ " --cwd PATH",
1395
+ " Starting directory for workspace discovery. Default: current directory.",
1396
+ " --no-discover-workspace",
1397
+ " Use --cwd exactly instead of searching upward for package.json, .brass-agent.json, or .git.",
1398
+ " --where, --print-workspace",
1399
+ " Print the resolved workspace root and exit.",
1400
+ " --config PATH",
1401
+ " Load a specific .brass-agent.json policy/config file.",
1402
+ " --save-run DIR",
1403
+ " Write final run JSON and Markdown artifacts to DIR.",
1404
+ " --batch-file PATH",
1405
+ " Run multiple goals sequentially from a JSON or line-based file.",
1406
+ " --batch-stop-on-failure",
1407
+ " Stop a batch after the first failed run.",
1408
+ " --batch-continue-on-failure",
1409
+ " Continue a batch even when a run fails.",
1410
+ " --doctor",
1411
+ " Check local CLI, workspace, VS Code, package manager, and LLM setup.",
1412
+ " --init",
1413
+ " Initialize this workspace with .brass-agent.json, brass-agent.batch.json, .env.example, and BRASS_AGENT.md.",
1414
+ " --force, --init-force",
1415
+ " Overwrite files generated by --init when they already exist.",
1416
+ " --init-profile default|google|openai-compatible|fake",
1417
+ " Initialization profile. Default: default, which leaves provider auto-detection enabled.",
1418
+ " --init-provider auto|fake|google|openai-compatible",
1419
+ " Alias for choosing an LLM-oriented init profile. auto maps to default.",
1420
+ " --init-dry-run",
1421
+ " Preview generated files without writing them.",
1422
+ " --no-config",
1423
+ " Do not discover or load an agent config file.",
1424
+ " --language auto|match-user|en|es|pt|fr|de|it",
1425
+ " Response language for LLM summaries. Default: config.language or auto-match the user goal.",
1426
+ " --env-file PATH",
1427
+ " Load Brass Agent environment variables from a specific env file.",
1428
+ " --no-env-file",
1429
+ " Do not auto-load .brass-agent.env, .env.local, or .env from --cwd.",
1430
+ " --json",
1431
+ " Print the full final AgentState JSON. Suppresses live event output.",
1432
+ " --ci",
1433
+ " Preserve output mode but set process exit codes from the final run status.",
1434
+ " --fail-on-patch-proposed",
1435
+ " In --ci mode, exit 2 when a patch was proposed but not applied.",
1436
+ " --events-json",
1437
+ " Stream AgentEvent objects as JSON Lines. Does not print the final AgentState.",
1438
+ " --protocol-json",
1439
+ " Stream Brass Agent protocol JSON Lines, including events and a final-state message.",
1440
+ " --protocol-full-patches",
1441
+ " Keep patch payloads untruncated in protocol/event JSON output for trusted local integrations.",
1442
+ " --patch-file PATH",
1443
+ " Supply a precomputed unified diff to the agent. Respects --mode.",
1444
+ " --apply-patch-file PATH",
1445
+ " Supply and apply a precomputed unified diff. Alias for --patch-file PATH --mode write.",
1446
+ " --rollback-patch-file PATH",
1447
+ " Reverse-apply a precomputed unified diff through PatchService. Requires write mode approvals.",
1448
+ " --yes, -y",
1449
+ " Auto-approve approval prompts. Useful for CI and smoke tests.",
1450
+ " --no-input",
1451
+ " Do not prompt; reject any action that requires approval.",
1452
+ " --approval auto|interactive|approve|deny",
1453
+ " Approval strategy. Default: auto, or config.approval if present.",
1454
+ " --help, -h",
1455
+ " Show this help message.",
1456
+ "",
1457
+ "Config files:",
1458
+ " brass-agent first resolves a workspace root by searching upward from --cwd.",
1459
+ " It looks for .brass-agent.json, brass-agent.config.json, package.json, workspace markers, or .git.",
1460
+ " brass-agent then searches upward from that workspace root for .brass-agent.json or brass-agent.config.json.",
1461
+ " config.batch.goals can define a default batch when --batch-file is not provided.",
1462
+ "",
1463
+ "Examples:",
1464
+ ' brass-agent "fix the failing tests"',
1465
+ " brass-agent --preset fix-tests",
1466
+ " brass-agent --preset inspect",
1467
+ " brass-agent --batch-file ./brass-agent.batch.json --ci",
1468
+ " brass-agent --where",
1469
+ " brass-agent --doctor",
1470
+ " brass-agent --doctor --json",
1471
+ " brass-agent --env-file .env --doctor",
1472
+ " brass-agent --init",
1473
+ " brass-agent --init --init-profile google",
1474
+ " brass-agent --init --init-profile fake --init-dry-run",
1475
+ ' brass-agent --config ./agent.policy.json "fix the failing tests"',
1476
+ ' brass-agent --no-config "fix the failing tests"',
1477
+ ' brass-agent --json "fix the failing tests"',
1478
+ ' brass-agent --events-json "fix the failing tests"',
1479
+ ' brass-agent --protocol-json "fix the failing tests"',
1480
+ ' brass-agent --protocol-json --protocol-full-patches "fix the failing tests"',
1481
+ ' brass-agent --apply-patch-file ./approved.diff --yes "apply approved patch"',
1482
+ ' brass-agent --apply "fix the failing tests"',
1483
+ ' brass-agent --apply --yes "fix the failing tests"',
1484
+ ' brass-agent --mode read-only --cwd ./repo "inspect the test failure"',
1485
+ "",
1486
+ "LLM providers:",
1487
+ " BRASS_LLM_PROVIDER=fake",
1488
+ " BRASS_LLM_PROVIDER=google GEMINI_API_KEY=...",
1489
+ " BRASS_LLM_PROVIDER=openai-compatible BRASS_LLM_ENDPOINT=... BRASS_LLM_API_KEY=..."
1490
+ ].join("\n"));
1491
+ };
1492
+ var envByName = (name) => name ? process.env[name] : void 0;
1493
+ var makeGoogleLLMFromEnv = (config) => {
1494
+ const apiKey = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(envByName(_optionalChain([config, 'optionalAccess', _39 => _39.apiKeyEnv])), () => ( process.env.BRASS_GOOGLE_API_KEY)), () => ( process.env.GOOGLE_API_KEY)), () => ( process.env.GEMINI_API_KEY));
1495
+ if (!apiKey) return void 0;
1496
+ return _chunkHRVX2IYWjs.makeGoogleGenerativeAILLM.call(void 0, {
1497
+ apiKey,
1498
+ model: _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(process.env.BRASS_GOOGLE_MODEL, () => ( process.env.BRASS_LLM_MODEL)), () => ( _optionalChain([config, 'optionalAccess', _40 => _40.model]))), () => ( "gemini-2.5-flash")),
1499
+ apiVersion: _nullishCoalesce(_nullishCoalesce(process.env.BRASS_GOOGLE_API_VERSION, () => ( _optionalChain([config, 'optionalAccess', _41 => _41.apiVersion]))), () => ( "v1beta")),
1500
+ baseUrl: _nullishCoalesce(process.env.BRASS_GOOGLE_BASE_URL, () => ( _optionalChain([config, 'optionalAccess', _42 => _42.baseUrl]))),
1501
+ endpoint: _nullishCoalesce(process.env.BRASS_GOOGLE_ENDPOINT, () => ( _optionalChain([config, 'optionalAccess', _43 => _43.endpoint]))),
1502
+ systemInstruction: _nullishCoalesce(process.env.BRASS_GOOGLE_SYSTEM_INSTRUCTION, () => ( _optionalChain([config, 'optionalAccess', _44 => _44.systemInstruction]))),
1503
+ temperature: _nullishCoalesce(parseOptionalNumber(process.env.BRASS_GOOGLE_TEMPERATURE), () => ( _optionalChain([config, 'optionalAccess', _45 => _45.temperature]))),
1504
+ topP: _nullishCoalesce(parseOptionalNumber(process.env.BRASS_GOOGLE_TOP_P), () => ( _optionalChain([config, 'optionalAccess', _46 => _46.topP]))),
1505
+ topK: _nullishCoalesce(parseOptionalNumber(process.env.BRASS_GOOGLE_TOP_K), () => ( _optionalChain([config, 'optionalAccess', _47 => _47.topK]))),
1506
+ maxOutputTokens: _nullishCoalesce(parseOptionalNumber(process.env.BRASS_GOOGLE_MAX_OUTPUT_TOKENS), () => ( _optionalChain([config, 'optionalAccess', _48 => _48.maxOutputTokens])))
1507
+ });
1508
+ };
1509
+ var makeOpenAICompatibleLLMFromEnv = (config) => {
1510
+ const endpoint = _nullishCoalesce(process.env.BRASS_LLM_ENDPOINT, () => ( _optionalChain([config, 'optionalAccess', _49 => _49.endpoint])));
1511
+ const apiKey = _nullishCoalesce(envByName(_optionalChain([config, 'optionalAccess', _50 => _50.apiKeyEnv])), () => ( process.env.BRASS_LLM_API_KEY));
1512
+ const model = _nullishCoalesce(_nullishCoalesce(process.env.BRASS_LLM_MODEL, () => ( _optionalChain([config, 'optionalAccess', _51 => _51.model]))), () => ( "gpt-4.1"));
1513
+ if (!endpoint || !apiKey) return void 0;
1514
+ return _chunkHRVX2IYWjs.makeOpenAICompatibleLLM.call(void 0, { endpoint, apiKey, model });
1515
+ };
1516
+ var makeLLMFromEnv = (config) => {
1517
+ const provider = _optionalChain([(_nullishCoalesce(process.env.BRASS_LLM_PROVIDER, () => ( _optionalChain([config, 'optionalAccess', _52 => _52.provider])))), 'optionalAccess', _53 => _53.trim, 'call', _54 => _54(), 'access', _55 => _55.toLowerCase, 'call', _56 => _56()]);
1518
+ const fakeResponse = _nullishCoalesce(process.env.BRASS_FAKE_LLM_RESPONSE, () => ( _optionalChain([config, 'optionalAccess', _57 => _57.fakeResponse])));
1519
+ if (provider === "fake") return _chunkHRVX2IYWjs.makeFakeLLM.call(void 0, { content: fakeResponse });
1520
+ if (provider === "google" || provider === "gemini") {
1521
+ const google = makeGoogleLLMFromEnv(config);
1522
+ if (!google) {
1523
+ throw new Error(
1524
+ "Google LLM provider requires BRASS_GOOGLE_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY, or config.llm.apiKeyEnv."
1525
+ );
1526
+ }
1527
+ return google;
1528
+ }
1529
+ if (provider === "openai" || provider === "openai-compatible") {
1530
+ const openAICompatible = makeOpenAICompatibleLLMFromEnv(config);
1531
+ if (!openAICompatible) {
1532
+ throw new Error(
1533
+ "OpenAI-compatible LLM provider requires BRASS_LLM_ENDPOINT/config.llm.endpoint and BRASS_LLM_API_KEY/config.llm.apiKeyEnv."
1534
+ );
1535
+ }
1536
+ return openAICompatible;
1537
+ }
1538
+ if (provider) {
1539
+ throw new Error(`Unsupported LLM provider: ${provider}`);
1540
+ }
1541
+ return _nullishCoalesce(_nullishCoalesce(makeGoogleLLMFromEnv(config), () => ( makeOpenAICompatibleLLMFromEnv(config))), () => ( _chunkHRVX2IYWjs.makeFakeLLM.call(void 0, { content: fakeResponse })));
1542
+ };
1543
+ var parseApprovalModeFromEnv = () => {
1544
+ const raw = _optionalChain([process, 'access', _58 => _58.env, 'access', _59 => _59.BRASS_AGENT_APPROVAL, 'optionalAccess', _60 => _60.trim, 'call', _61 => _61(), 'access', _62 => _62.toLowerCase, 'call', _63 => _63()]);
1545
+ if (!raw) return void 0;
1546
+ if (isCliApprovalMode(raw)) return raw;
1547
+ throw new Error("BRASS_AGENT_APPROVAL must be one of: auto, interactive, approve, deny");
1548
+ };
1549
+ var envTruthy = (value) => value === "1" || _optionalChain([value, 'optionalAccess', _64 => _64.toLowerCase, 'call', _65 => _65()]) === "true" || _optionalChain([value, 'optionalAccess', _66 => _66.toLowerCase, 'call', _67 => _67()]) === "yes";
1550
+ var canPromptInteractively = () => Boolean(_optionalChain([process, 'access', _68 => _68.stdin, 'optionalAccess', _69 => _69.isTTY]) && (_nullishCoalesce(_optionalChain([process, 'access', _70 => _70.stderr, 'optionalAccess', _71 => _71.isTTY]), () => ( _optionalChain([process, 'access', _72 => _72.stdout, 'optionalAccess', _73 => _73.isTTY])))));
1551
+ var resolveApprovalMode = (parsed) => {
1552
+ if (parsed.approvalSpecified && parsed.approval !== "auto") return parsed.approval;
1553
+ if (envTruthy(process.env.BRASS_AGENT_AUTO_APPROVE)) return "approve";
1554
+ const fromEnv = parseApprovalModeFromEnv();
1555
+ if (fromEnv && fromEnv !== "auto") return fromEnv;
1556
+ if (parsed.approval !== "auto") return parsed.approval;
1557
+ if (parsed.output === "human" && canPromptInteractively()) return "interactive";
1558
+ return "deny";
1559
+ };
1560
+ var makeApprovalServiceFromCli = (parsed) => {
1561
+ const mode = resolveApprovalMode(parsed);
1562
+ switch (mode) {
1563
+ case "approve":
1564
+ return _chunkHRVX2IYWjs.autoApproveApprovals;
1565
+ case "deny":
1566
+ return _chunkHRVX2IYWjs.makeAutoDenyApprovals.call(void 0, "Approval rejected because the CLI is running without interactive input. Use --yes to auto-approve.");
1567
+ case "interactive":
1568
+ return makeCliApprovalService();
1569
+ }
1570
+ };
1571
+ var truncate = (value, max = 2e3) => {
1572
+ if (value.length <= max) return value;
1573
+ return `${value.slice(0, max)}
1574
+ \u2026 truncated ${value.length - max} chars`;
1575
+ };
1576
+ var compactText = (value, max = 1e3) => {
1577
+ if (value.length <= max) return value;
1578
+ return `${value.slice(0, max)}\u2026 truncated ${value.length - max} chars`;
1579
+ };
1580
+ var compactPatchText = (value, options) => options.fullPatches ? value : compactText(value);
1581
+ var compactGoal = (goal, options) => ({
1582
+ ...goal,
1583
+ ...goal.initialPatch ? { initialPatch: compactPatchText(goal.initialPatch, options) } : {}
1584
+ });
1585
+ var compactAction = (action, options = {}) => {
1586
+ switch (action.type) {
1587
+ case "llm.complete":
1588
+ return { ...action, prompt: compactText(action.prompt) };
1589
+ case "patch.apply":
1590
+ case "patch.rollback":
1591
+ case "patch.propose":
1592
+ return { ...action, patch: compactPatchText(action.patch, options) };
1593
+ default:
1594
+ return action;
1595
+ }
1596
+ };
1597
+ var compactObservation = (observation, options = {}) => {
1598
+ switch (observation.type) {
1599
+ case "fs.fileRead":
1600
+ return { ...observation, content: compactText(observation.content) };
1601
+ case "llm.response":
1602
+ return { ...observation, content: compactText(observation.content) };
1603
+ case "shell.result":
1604
+ return {
1605
+ ...observation,
1606
+ stdout: compactText(observation.stdout),
1607
+ stderr: compactText(observation.stderr)
1608
+ };
1609
+ case "fs.searchResult":
1610
+ return {
1611
+ ...observation,
1612
+ matches: observation.matches.slice(0, 30),
1613
+ omittedMatches: Math.max(0, observation.matches.length - 30)
1614
+ };
1615
+ case "patch.proposed":
1616
+ return { ...observation, patch: compactPatchText(observation.patch, options) };
1617
+ case "patch.applied":
1618
+ case "patch.rolledBack":
1619
+ return observation.patch ? { ...observation, patch: compactPatchText(observation.patch, options) } : observation;
1620
+ default:
1621
+ return observation;
1622
+ }
1623
+ };
1624
+ var compactAgentEvent = (event, options = {}) => {
1625
+ switch (event.type) {
1626
+ case "agent.run.started":
1627
+ case "agent.run.completed":
1628
+ return { ...event, goal: compactGoal(event.goal, options) };
1629
+ case "agent.action.started":
1630
+ return { ...event, action: compactAction(event.action, options) };
1631
+ case "agent.action.completed":
1632
+ return { ...event, action: compactAction(event.action, options), observation: compactObservation(event.observation, options) };
1633
+ case "agent.action.failed":
1634
+ return { ...event, action: compactAction(event.action, options) };
1635
+ case "agent.observation.recorded":
1636
+ return { ...event, observation: compactObservation(event.observation, options) };
1637
+ case "agent.tool.timeout":
1638
+ case "agent.permission.denied":
1639
+ case "agent.approval.requested":
1640
+ case "agent.approval.resolved":
1641
+ return { ...event, action: compactAction(event.action, options) };
1642
+ default:
1643
+ return event;
1644
+ }
1645
+ };
1646
+ var compactAgentState = (state, options = {}) => ({
1647
+ ...state,
1648
+ goal: compactGoal(state.goal, options),
1649
+ observations: state.observations.map((observation) => compactObservation(observation, options)),
1650
+ errors: state.errors.map((error) => {
1651
+ switch (error._tag) {
1652
+ case "PermissionDenied":
1653
+ case "ApprovalRejected":
1654
+ return { ...error, action: compactAction(error.action, options) };
1655
+ case "PatchError":
1656
+ return {
1657
+ ...error,
1658
+ cause: String(error.cause),
1659
+ ...error.patch ? { patch: compactPatchText(error.patch, options) } : {}
1660
+ };
1661
+ case "FsError":
1662
+ case "ShellError":
1663
+ case "LLMError":
1664
+ return { ...error, cause: String(error.cause) };
1665
+ default:
1666
+ return error;
1667
+ }
1668
+ })
1669
+ });
1670
+ var protocolEnvelope = (message) => ({
1671
+ protocol: "brass-agent",
1672
+ version: 1,
1673
+ ...message
1674
+ });
1675
+ var statusIcon2 = (status) => {
1676
+ switch (status) {
1677
+ case "ok":
1678
+ return "\u2713";
1679
+ case "warn":
1680
+ return "!";
1681
+ case "fail":
1682
+ return "\u2717";
1683
+ }
1684
+ };
1685
+ var formatDuration = (durationMs) => `${Math.max(0, durationMs)}ms`;
1686
+ var latestObservation = (state, type) => [...state.observations].reverse().find((obs) => obs.type === type);
1687
+ var createHumanEventSink = (configPath) => ({
1688
+ emit(event) {
1689
+ switch (event.type) {
1690
+ case "agent.run.started":
1691
+ console.log(`brass-agent ${event.goal.mode}`);
1692
+ console.log(`workspace: ${event.goal.cwd}`);
1693
+ if (configPath) console.log(`config: ${configPath}`);
1694
+ console.log(`goal: ${event.goal.text}`);
1695
+ console.log("");
1696
+ break;
1697
+ case "agent.action.started":
1698
+ console.log(`\u2192 ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, event.action)}`);
1699
+ break;
1700
+ case "agent.action.completed": {
1701
+ const status = _chunkHRVX2IYWjs.observationStatus.call(void 0, event.observation);
1702
+ console.log(`${statusIcon2(status)} ${_chunkHRVX2IYWjs.summarizeAgentObservation.call(void 0, event.observation)} ${formatDuration(event.durationMs)}`);
1703
+ break;
1704
+ }
1705
+ case "agent.action.failed":
1706
+ if (event.error._tag !== "ToolTimeout" && event.error._tag !== "PermissionDenied" && event.error._tag !== "ApprovalRejected") {
1707
+ console.log(`\u2717 ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, event.action)} failed with ${event.error._tag} ${formatDuration(event.durationMs)}`);
1708
+ }
1709
+ break;
1710
+ case "agent.tool.timeout":
1711
+ console.log(`! ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, event.action)} timed out after ${event.timeoutMs}ms`);
1712
+ break;
1713
+ case "agent.permission.denied":
1714
+ console.log(`\u2717 ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, event.action)} denied: ${event.reason}`);
1715
+ break;
1716
+ case "agent.approval.requested":
1717
+ console.log(`? approval required for ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, event.action)} (${event.risk})`);
1718
+ break;
1719
+ case "agent.approval.resolved":
1720
+ if (event.approved) {
1721
+ console.log(`\u2713 approval granted for ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, event.action)}`);
1722
+ } else {
1723
+ console.log(`\u2717 approval rejected for ${_chunkHRVX2IYWjs.summarizeAgentAction.call(void 0, event.action)}${event.reason ? `: ${event.reason}` : ""}`);
1724
+ }
1725
+ break;
1726
+ case "agent.patch.applied":
1727
+ break;
1728
+ case "agent.patch.rolledBack":
1729
+ if (event.automatic) {
1730
+ console.log(`\u2713 automatic rollback completed (${event.changedFiles.join(", ") || "no files reported"})`);
1731
+ }
1732
+ break;
1733
+ case "agent.observation.recorded":
1734
+ case "agent.run.completed":
1735
+ break;
1736
+ }
1737
+ }
1738
+ });
1739
+ var createJsonEventSink = (options = {}) => ({
1740
+ emit(event) {
1741
+ console.log(JSON.stringify(compactAgentEvent(event, options)));
1742
+ }
1743
+ });
1744
+ var createProtocolEventSink = (options = {}) => ({
1745
+ emit(event) {
1746
+ console.log(JSON.stringify(protocolEnvelope({ type: "event", event: compactAgentEvent(event, options) })));
1747
+ }
1748
+ });
1749
+ var safeFilePart = (value) => value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "run";
1750
+ var markdownEscape = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
1751
+ var writeRunArtifacts = _chunkTGOMLZ65js.async.call(void 0, state, outputDir, options) => {
1752
+ const nodePath = await dynamicImport2("node:path");
1753
+ const nodeFs = await dynamicImport2("node:fs/promises");
1754
+ const dir = nodePath.isAbsolute(outputDir) ? outputDir : nodePath.resolve(state.goal.cwd, outputDir);
1755
+ await nodeFs.mkdir(dir, { recursive: true });
1756
+ const id = safeFilePart(state.goal.id);
1757
+ const base = `${id}-${safeFilePart(state.goal.text)}`;
1758
+ const jsonPath = nodePath.join(dir, `${base}.json`);
1759
+ const mdPath = nodePath.join(dir, `${base}.md`);
1760
+ const done = latestObservation(state, "agent.done");
1761
+ const error = latestObservation(state, "agent.error");
1762
+ const appliedPatch = latestObservation(state, "patch.applied");
1763
+ const rolledBackPatch = latestObservation(state, "patch.rolledBack");
1764
+ await nodeFs.writeFile(jsonPath, `${JSON.stringify(compactAgentState(state, options), null, 2)}
1765
+ `, "utf8");
1766
+ await nodeFs.writeFile(mdPath, [
1767
+ `# Brass Agent Run ${markdownEscape(state.goal.id)}`,
1768
+ "",
1769
+ `- Goal: ${markdownEscape(state.goal.text)}`,
1770
+ `- Workspace: ${markdownEscape(state.goal.cwd)}`,
1771
+ `- Mode: ${state.goal.mode}`,
1772
+ `- Phase: ${state.phase}`,
1773
+ `- Steps: ${state.steps}`,
1774
+ appliedPatch ? `- Changed files: ${appliedPatch.changedFiles.join(", ") || "none reported"}` : void 0,
1775
+ rolledBackPatch ? `- Rolled back files: ${rolledBackPatch.changedFiles.join(", ") || "none reported"}` : void 0,
1776
+ "",
1777
+ "## Summary",
1778
+ "",
1779
+ _optionalChain([done, 'optionalAccess', _74 => _74.summary, 'optionalAccess', _75 => _75.trim, 'call', _76 => _76()]) || (error ? `Error: ${error.error._tag}` : "No summary recorded.")
1780
+ ].filter(Boolean).join("\n"), "utf8");
1781
+ if (_optionalChain([process, 'access', _77 => _77.stderr, 'optionalAccess', _78 => _78.isTTY])) {
1782
+ console.error(`saved run artifacts: ${jsonPath} ${mdPath}`);
1783
+ }
1784
+ };
1785
+ var latestShellResult = (state) => latestObservation(state, "shell.result");
1786
+ var computeCiExitCode = (state, options) => {
1787
+ if (latestObservation(state, "agent.error")) return 1;
1788
+ const latestShell = latestShellResult(state);
1789
+ if (latestShell && latestShell.exitCode !== 0) return 1;
1790
+ if (options.failOnPatchProposed && latestObservation(state, "patch.proposed") && !latestObservation(state, "patch.applied")) {
1791
+ return 2;
1792
+ }
1793
+ return 0;
1794
+ };
1795
+ var printHumanFinalSummary = (state) => {
1796
+ const done = latestObservation(state, "agent.done");
1797
+ const error = latestObservation(state, "agent.error");
1798
+ const proposedPatch = latestObservation(state, "patch.proposed");
1799
+ const appliedPatch = latestObservation(state, "patch.applied");
1800
+ const rolledBackPatch = latestObservation(state, "patch.rolledBack");
1801
+ const llmResponse = latestObservation(state, "llm.response");
1802
+ console.log("");
1803
+ console.log(`phase: ${state.phase}`);
1804
+ console.log(`steps: ${state.steps}`);
1805
+ if (rolledBackPatch) {
1806
+ console.log(`rolled back files: ${rolledBackPatch.changedFiles.join(", ") || "(none reported)"}`);
1807
+ } else if (appliedPatch) {
1808
+ console.log(`changed files: ${appliedPatch.changedFiles.join(", ") || "(none reported)"}`);
1809
+ } else if (proposedPatch) {
1810
+ console.log("patch: proposed only; rerun with --apply to apply it");
1811
+ }
1812
+ if (done) {
1813
+ console.log("");
1814
+ console.log("summary:");
1815
+ console.log(truncate(done.summary.trim() || "Agent completed."));
1816
+ } else if (error) {
1817
+ console.log("");
1818
+ console.log("error:");
1819
+ console.log(JSON.stringify(error.error, null, 2));
1820
+ } else if (llmResponse) {
1821
+ console.log("");
1822
+ console.log("llm response:");
1823
+ console.log(truncate(llmResponse.content.trim()));
1824
+ }
1825
+ };
1826
+ var makeEventsSink = (parsed, compactOptions) => parsed.output === "human" ? createHumanEventSink(parsed.resolvedConfigPath) : parsed.output === "events-json" ? createJsonEventSink(compactOptions) : parsed.output === "protocol-json" ? createProtocolEventSink(compactOptions) : void 0;
1827
+ var makeAgentEnv = (parsed, events) => {
1828
+ const shell = _chunkHRVX2IYWjs.NodeShell;
1829
+ return {
1830
+ shell,
1831
+ fs: _chunkHRVX2IYWjs.makeNodeFileSystem.call(void 0, shell),
1832
+ patch: _chunkHRVX2IYWjs.makeNodePatchService.call(void 0, shell),
1833
+ llm: makeLLMFromEnv(parsed.config.llm),
1834
+ permissions: _chunkHRVX2IYWjs.makeConfiguredPermissions.call(void 0, parsed.config.permissions),
1835
+ approvals: makeApprovalServiceFromCli(parsed),
1836
+ ...events ? { events } : {},
1837
+ ...parsed.config.tools ? { toolPolicies: parsed.config.tools } : {}
1838
+ };
1839
+ };
1840
+ var singleRunFromParsed = (parsed) => ({
1841
+ index: 0,
1842
+ cwd: parsed.cwd,
1843
+ goalText: parsed.goalText,
1844
+ mode: parsed.mode,
1845
+ patchFileMode: parsed.patchFileMode,
1846
+ ...parsed.patchFile ? { patchFile: parsed.patchFile } : {},
1847
+ ...parsed.saveRunDir ? { saveRunDir: parsed.saveRunDir } : {}
1848
+ });
1849
+ var runCliAgent = _chunkTGOMLZ65js.async.call(void 0, parsed, run, compactOptions, events) => {
1850
+ const env = makeAgentEnv(parsed, events);
1851
+ const runtime = new (0, _chunkTGOMLZ65js.Runtime)({ env });
1852
+ const initialPatch = run.patchFile ? await readPatchFile(run.cwd, run.patchFile) : void 0;
1853
+ const state = await runtime.toPromise(
1854
+ _chunkHRVX2IYWjs.runAgent.call(void 0, runtime, {
1855
+ id: `agent-${Date.now()}-${run.index + 1}`,
1856
+ cwd: run.cwd,
1857
+ text: run.goalText,
1858
+ mode: run.mode,
1859
+ ...parsed.config.project ? { project: parsed.config.project } : {},
1860
+ ...parsed.config.context ? { context: parsed.config.context } : {},
1861
+ ...parsed.config.patchQuality ? { patchQuality: parsed.config.patchQuality } : {},
1862
+ ...parsed.config.rollback ? { rollback: parsed.config.rollback } : {},
1863
+ ...parsed.config.redaction ? { redaction: parsed.config.redaction } : {},
1864
+ ...parsed.language ? { language: { response: parsed.language } } : parsed.config.language ? { language: parsed.config.language } : {},
1865
+ ...initialPatch ? { initialPatch, initialPatchMode: run.patchFileMode } : {}
1866
+ })
1867
+ );
1868
+ if (run.saveRunDir) {
1869
+ await writeRunArtifacts(state, run.saveRunDir, compactOptions);
1870
+ }
1871
+ return {
1872
+ run,
1873
+ state,
1874
+ exitCode: computeCiExitCode(state, { failOnPatchProposed: parsed.failOnPatchProposed })
1875
+ };
1876
+ };
1877
+ var computeBatchExitCode = (results) => {
1878
+ if (results.some((result) => result.exitCode === 1)) return 1;
1879
+ if (results.some((result) => result.exitCode === 2)) return 2;
1880
+ return 0;
1881
+ };
1882
+ var batchSummary = (runs, results) => ({
1883
+ total: runs.length,
1884
+ completed: results.length,
1885
+ failed: results.filter((result) => result.exitCode !== 0).length,
1886
+ exitCode: computeBatchExitCode(results),
1887
+ stoppedEarly: results.length < runs.length
1888
+ });
1889
+ var printHumanBatchSummary = (runs, results) => {
1890
+ const summary = batchSummary(runs, results);
1891
+ console.log("");
1892
+ console.log("batch summary:");
1893
+ console.log(`completed: ${summary.completed}/${summary.total}`);
1894
+ console.log(`failed: ${summary.failed}`);
1895
+ if (summary.stoppedEarly) console.log("stopped early: yes");
1896
+ console.log(`exit code: ${summary.exitCode}`);
1897
+ };
1898
+ var printWorkspaceWhere = (parsed) => {
1899
+ const result = {
1900
+ cwd: parsed.cwd,
1901
+ inputCwd: parsed.workspaceDiscovery.inputCwd,
1902
+ changed: parsed.workspaceDiscovery.changed,
1903
+ disabled: Boolean(parsed.workspaceDiscovery.disabled),
1904
+ marker: parsed.workspaceDiscovery.marker,
1905
+ markerPath: parsed.workspaceDiscovery.markerPath,
1906
+ configPath: parsed.resolvedConfigPath,
1907
+ envFiles: parsed.envFileLoad.paths
1908
+ };
1909
+ if (parsed.output === "json" || parsed.output === "protocol-json") {
1910
+ console.log(JSON.stringify(result, null, 2));
1911
+ return;
1912
+ }
1913
+ console.log("brass-agent workspace");
1914
+ console.log(`input: ${result.inputCwd}`);
1915
+ console.log(`workspace: ${result.cwd}`);
1916
+ if (result.disabled) console.log("discovery: disabled");
1917
+ else if (result.marker) console.log(`marker: ${result.marker} (${result.markerPath})`);
1918
+ else console.log("marker: none found; using input cwd");
1919
+ if (result.configPath) console.log(`config: ${result.configPath}`);
1920
+ if (result.envFiles.length > 0) console.log(`env: ${result.envFiles.join(", ")}`);
1921
+ };
1922
+ var main = _chunkTGOMLZ65js.async.call(void 0, ) => {
1923
+ const parsed = await resolveParsedConfig(parseCliArgs(process.argv.slice(2)));
1924
+ const isBatch = parsed.batchRuns.length > 0;
1925
+ if (parsed.showHelp) {
1926
+ printHelp();
1927
+ process.exit(0);
1928
+ }
1929
+ if (parsed.where) {
1930
+ printWorkspaceWhere(parsed);
1931
+ return;
1932
+ }
1933
+ if (parsed.init) {
1934
+ const result2 = await initializeAgentWorkspace({
1935
+ cwd: parsed.cwd,
1936
+ force: parsed.initForce,
1937
+ dryRun: parsed.initDryRun,
1938
+ profile: parsed.initProfile
1939
+ });
1940
+ if (parsed.output === "json") {
1941
+ console.log(JSON.stringify(result2, null, 2));
1942
+ } else {
1943
+ printAgentInitResult(result2);
1944
+ }
1945
+ return;
1946
+ }
1947
+ if (parsed.doctor) {
1948
+ const report = await runAgentDoctor({
1949
+ cwd: parsed.cwd,
1950
+ config: parsed.config,
1951
+ configPath: parsed.resolvedConfigPath,
1952
+ envFileLoad: parsed.envFileLoad,
1953
+ workspaceDiscovery: parsed.workspaceDiscovery
1954
+ });
1955
+ if (parsed.output === "json") {
1956
+ console.log(JSON.stringify(report, null, 2));
1957
+ } else {
1958
+ printAgentDoctorReport(report);
1959
+ }
1960
+ process.exitCode = report.status === "fail" ? 1 : 0;
1961
+ return;
1962
+ }
1963
+ if (!parsed.goalText && !isBatch) {
1964
+ printHelp();
1965
+ process.exit(1);
1966
+ }
1967
+ const compactOptions = {
1968
+ fullPatches: parsed.protocolFullPatches
1969
+ };
1970
+ const events = makeEventsSink(parsed, compactOptions);
1971
+ const runs = isBatch ? parsed.batchRuns : [singleRunFromParsed(parsed)];
1972
+ const results = [];
1973
+ for (const run of runs) {
1974
+ const result2 = await runCliAgent(parsed, run, compactOptions, events);
1975
+ results.push(result2);
1976
+ if (parsed.output === "protocol-json") {
1977
+ console.log(JSON.stringify(protocolEnvelope({ type: "final-state", state: compactAgentState(result2.state, compactOptions) })));
1978
+ } else if (parsed.output === "human") {
1979
+ printHumanFinalSummary(result2.state);
1980
+ }
1981
+ if (isBatch && parsed.batchStopOnFailureResolved && result2.exitCode !== 0) {
1982
+ break;
1983
+ }
1984
+ }
1985
+ if (isBatch) {
1986
+ const summary = batchSummary(runs, results);
1987
+ if (parsed.output === "json") {
1988
+ console.log(JSON.stringify({
1989
+ type: "batch",
1990
+ summary,
1991
+ results: results.map((result2) => ({
1992
+ index: result2.run.index,
1993
+ goal: result2.run.goalText,
1994
+ cwd: result2.run.cwd,
1995
+ mode: result2.run.mode,
1996
+ exitCode: result2.exitCode,
1997
+ state: compactAgentState(result2.state, compactOptions)
1998
+ }))
1999
+ }, null, 2));
2000
+ } else if (parsed.output === "protocol-json") {
2001
+ console.log(JSON.stringify(protocolEnvelope({ type: "batch-summary", summary })));
2002
+ } else if (parsed.output === "human") {
2003
+ printHumanBatchSummary(runs, results);
2004
+ }
2005
+ if (parsed.ci) {
2006
+ process.exitCode = summary.exitCode;
2007
+ }
2008
+ return;
2009
+ }
2010
+ const result = results[0];
2011
+ if (!result) throw new Error("Agent run did not produce a result.");
2012
+ if (parsed.output === "json") {
2013
+ console.log(JSON.stringify(result.state, null, 2));
2014
+ }
2015
+ if (parsed.ci) {
2016
+ process.exitCode = result.exitCode;
2017
+ }
2018
+ };
2019
+ main().catch((error) => {
2020
+ console.error(error);
2021
+ process.exit(1);
2022
+ });