@burtson-labs/bandit-stealth-cli 1.7.119 → 1.7.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +2 -2
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1225,7 +1225,7 @@ ${(()=>{let G=`Bandit insights \u2014 ${new Date(s).toISOString().slice(0,10)}`,
1225
1225
  <h1>You're signed in.</h1>
1226
1226
  <p>Bandit picked up your session. You can close this tab and return to your terminal.</p>
1227
1227
  </div>
1228
- </body></html>`;t(B1r,"startLoopbackListener");t(z1r,"openBrowser");t(U1r,"buildDefaultDeviceLabel");t(J1r,"runOAuthSignIn")});var due=Nt((M4r,q1r)=>{q1r.exports={name:"@burtson-labs/bandit-stealth-cli",version:"1.7.119",description:"Bandit \u2014 a local-first AI coding agent for your terminal. Same runtime as the Bandit Stealth VS Code / Cursor extension.",keywords:["ai","agent","cli","coding-agent","llm","ollama","local-first","bandit","burtson-labs","terminal","repl","developer-tools"],homepage:"https://burtson.ai",bugs:{email:"team@burtson.ai"},license:"MIT",author:{name:"Burtson Labs",email:"team@burtson.ai",url:"https://burtson.ai"},bin:{bandit:"./dist/cli.js"},main:"dist/cli.js",files:["dist/cli.js","README.md","LICENSE"],engines:{node:">=20"},publishConfig:{access:"public"},scripts:{typecheck:"tsc -p tsconfig.json --noEmit",build:"node build.mjs","build:publish":"node build.mjs --publish",dev:"node build.mjs --watch",start:"node dist/cli.js",smoke:"node build.mjs && node dist/__smoke__/smoke.js",integration:"node build.mjs && node dist/__integration__/ollama.js",eval:"node build.mjs && node dist/__eval__/eval.js",benchmark:"node build.mjs && node dist/__eval__/benchmark.js","gen-logo":"node scripts/gen-logo.mjs","preview-banner":"node scripts/preview-banner.mjs",clean:"rm -rf dist",prepack:"node scripts/prepack.mjs",postpack:"node scripts/postpack.mjs",prepublishOnly:"pnpm run clean && pnpm run typecheck && pnpm run build:publish"},dependencies:{"pdf-parse":"^2.4.5"},devDependencies:{"@burtson-labs/agent-core":"workspace:*","@burtson-labs/host-kit":"workspace:*","@burtson-labs/stealth-core-runtime":"workspace:*","@types/node":"^20.11.0","@types/pdf-parse":"^1.1.5","@types/pngjs":"^6.0.5",esbuild:"^0.28.0",pngjs:"^7.0.0",typescript:"^5.4.0"}}});var mbr={};module.exports=qYe(mbr);var Zm=hu(require("fs")),kOe=hu(require("os")),gm=hu(require("path")),l9=hu(require("readline")),mue=hu(require("child_process")),Jp=hu(BV()),b6=hu(J6e());var jF=hu(require("fs")),q6e=hu(require("os")),_6=hu(require("path")),eZ=hu(require("child_process"));function N2(a){return a==="~"?q6e.homedir():a.startsWith("~/")?_6.join(q6e.homedir(),a.slice(2)):a}t(N2,"expandHome");var t9=16*1024,YV=32*1024,cft=1e4,D0r=3e4,W6e=200,V6e=new Set(["node_modules",".git","dist","build","out",".next",".turbo","coverage","target","__pycache__",".venv","venv"]);function I0r(a){let s=t(d=>{let f=d.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!f)return[d];let[,v,x,I]=f;return x.split(",").map(R=>`${v}${R.trim()}${I}`)},"braceExpand"),l=a.match(/^([^*{}]+?)\/\*\*\/(.+)$/);if(l){let[,d,f]=l;return{includes:s(f),subDir:d}}return{includes:s(a),subDir:""}}t(I0r,"expandGlobForGrep");var LF=class{constructor(s,l,d={}){this.workspaceRoot=s;this.languageAdapters=l;this.options=d;this._readFiles=new Set}static{t(this,"CliToolExecutionContext")}markFileRead(s){this._readFiles.add(N2(s))}hasFileBeenRead(s){return this._readFiles.has(N2(s))}async readFile(s){return jF.promises.readFile(N2(s),"utf-8")}async writeFile(s,l){let d=N2(s);if(this.options.approveWrite&&!await this.options.approveWrite(d,l))throw new Error(`Write to ${d} rejected by user`);await jF.promises.mkdir(_6.dirname(d),{recursive:!0}),await jF.promises.writeFile(d,l,"utf-8")}async listFiles(s,l){let d=N2(l??this.workspaceRoot),f=A0r(s),v=[];return await lft(d,d,f,v),v.slice(0,W6e).sort()}async listDirectoryEntries(s){let l=N2(s),d=await jF.promises.readdir(l,{withFileTypes:!0}),f=[];for(let v of d){if(v.name.startsWith("."))continue;let x=v.isDirectory();if(v.isSymbolicLink())try{x=(await jF.promises.stat(_6.join(l,v.name))).isDirectory()}catch{x=!1}f.push(x?`${v.name}/`:v.name)}return f.sort()}async searchCode(s,l,d){let f=N2(l??this.workspaceRoot);return this.runRipgrep(s,f,d).catch(()=>this.runGrep(s,f,d))}async runCommand(s,l,d){let f=l.map(N2),v=d?N2(d):this.workspaceRoot;return new Promise(x=>{let I="",R="",B=eZ.spawn(s,f,{cwd:v,shell:process.platform==="win32",env:{...process.env}}),q=setTimeout(()=>{B.kill("SIGTERM"),x({stdout:I.slice(0,YV),stderr:R+`
1228
+ </body></html>`;t(B1r,"startLoopbackListener");t(z1r,"openBrowser");t(U1r,"buildDefaultDeviceLabel");t(J1r,"runOAuthSignIn")});var due=Nt((M4r,q1r)=>{q1r.exports={name:"@burtson-labs/bandit-stealth-cli",version:"1.7.121",description:"Bandit \u2014 a local-first AI coding agent for your terminal. Same runtime as the Bandit Stealth VS Code / Cursor extension.",keywords:["ai","agent","cli","coding-agent","llm","ollama","local-first","bandit","burtson-labs","terminal","repl","developer-tools"],homepage:"https://burtson.ai",bugs:{email:"team@burtson.ai"},license:"MIT",author:{name:"Burtson Labs",email:"team@burtson.ai",url:"https://burtson.ai"},bin:{bandit:"./dist/cli.js"},main:"dist/cli.js",files:["dist/cli.js","README.md","LICENSE"],engines:{node:">=20"},publishConfig:{access:"public"},scripts:{typecheck:"tsc -p tsconfig.json --noEmit",build:"node build.mjs","build:publish":"node build.mjs --publish",dev:"node build.mjs --watch",start:"node dist/cli.js",smoke:"node build.mjs && node dist/__smoke__/smoke.js",integration:"node build.mjs && node dist/__integration__/ollama.js",eval:"node build.mjs && node dist/__eval__/eval.js",benchmark:"node build.mjs && node dist/__eval__/benchmark.js","gen-logo":"node scripts/gen-logo.mjs","preview-banner":"node scripts/preview-banner.mjs",clean:"rm -rf dist",prepack:"node scripts/prepack.mjs",postpack:"node scripts/postpack.mjs",prepublishOnly:"pnpm run clean && pnpm run typecheck && pnpm run build:publish"},dependencies:{"pdf-parse":"^2.4.5"},devDependencies:{"@burtson-labs/agent-core":"workspace:*","@burtson-labs/host-kit":"workspace:*","@burtson-labs/stealth-core-runtime":"workspace:*","@types/node":"^20.11.0","@types/pdf-parse":"^1.1.5","@types/pngjs":"^6.0.5",esbuild:"^0.28.0",pngjs:"^7.0.0",typescript:"^5.4.0"}}});var mbr={};module.exports=qYe(mbr);var Zm=hu(require("fs")),kOe=hu(require("os")),gm=hu(require("path")),l9=hu(require("readline")),mue=hu(require("child_process")),Jp=hu(BV()),b6=hu(J6e());var jF=hu(require("fs")),q6e=hu(require("os")),_6=hu(require("path")),eZ=hu(require("child_process"));function N2(a){return a==="~"?q6e.homedir():a.startsWith("~/")?_6.join(q6e.homedir(),a.slice(2)):a}t(N2,"expandHome");var t9=16*1024,YV=32*1024,cft=1e4,D0r=3e4,W6e=200,V6e=new Set(["node_modules",".git","dist","build","out",".next",".turbo","coverage","target","__pycache__",".venv","venv"]);function I0r(a){let s=t(d=>{let f=d.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!f)return[d];let[,v,x,I]=f;return x.split(",").map(R=>`${v}${R.trim()}${I}`)},"braceExpand"),l=a.match(/^([^*{}]+?)\/\*\*\/(.+)$/);if(l){let[,d,f]=l;return{includes:s(f),subDir:d}}return{includes:s(a),subDir:""}}t(I0r,"expandGlobForGrep");var LF=class{constructor(s,l,d={}){this.workspaceRoot=s;this.languageAdapters=l;this.options=d;this._readFiles=new Set}static{t(this,"CliToolExecutionContext")}markFileRead(s){this._readFiles.add(N2(s))}hasFileBeenRead(s){return this._readFiles.has(N2(s))}async readFile(s){return jF.promises.readFile(N2(s),"utf-8")}async writeFile(s,l){let d=N2(s);if(this.options.approveWrite&&!await this.options.approveWrite(d,l))throw new Error(`Write to ${d} rejected by user`);await jF.promises.mkdir(_6.dirname(d),{recursive:!0}),await jF.promises.writeFile(d,l,"utf-8")}async listFiles(s,l){let d=N2(l??this.workspaceRoot),f=A0r(s),v=[];return await lft(d,d,f,v),v.slice(0,W6e).sort()}async listDirectoryEntries(s){let l=N2(s),d=await jF.promises.readdir(l,{withFileTypes:!0}),f=[];for(let v of d){if(v.name.startsWith("."))continue;let x=v.isDirectory();if(v.isSymbolicLink())try{x=(await jF.promises.stat(_6.join(l,v.name))).isDirectory()}catch{x=!1}f.push(x?`${v.name}/`:v.name)}return f.sort()}async searchCode(s,l,d){let f=N2(l??this.workspaceRoot);return this.runRipgrep(s,f,d).catch(()=>this.runGrep(s,f,d))}async runCommand(s,l,d){let f=l.map(N2),v=d?N2(d):this.workspaceRoot;return new Promise(x=>{let I="",R="",B=eZ.spawn(s,f,{cwd:v,shell:process.platform==="win32",env:{...process.env}}),q=setTimeout(()=>{B.kill("SIGTERM"),x({stdout:I.slice(0,YV),stderr:R+`
1229
1229
  [process timed out]`,exitCode:124})},D0r);B.stdout?.on("data",G=>{I+=G.toString(),I.length>YV&&B.kill("SIGTERM")}),B.stderr?.on("data",G=>{R+=G.toString()}),B.on("close",G=>{clearTimeout(q),x({stdout:I.slice(0,YV),stderr:R.slice(0,4*1024),exitCode:G??0})}),B.on("error",G=>{if(clearTimeout(q),G.code==="ENOENT"){x({stdout:"",stderr:`spawn ${s} ENOENT \u2014 '${s}' not found on PATH. Verify the tool is installed (\`which ${s}\` in a fresh terminal). If you use nvm/asdf/volta, your shim PATH may not be inherited; relaunching this CLI from the same terminal session that has \`${s}\` on PATH usually fixes it.`,exitCode:127});return}x({stdout:"",stderr:G.message,exitCode:1})})})}async watchCommand(s,l,d,f){let v=l.map(N2),x=d?N2(d):this.workspaceRoot;return new Promise(I=>{let R="",B="",q=!1,G=!1,W=eZ.spawn(s,v,{cwd:x,shell:process.platform==="win32",env:{...process.env}}),ve=t(Pe=>{G||(G=!0,I({stdout:R.slice(0,YV),stderr:B.slice(0,4*1024),exitCode:Pe,endedEarly:q}))},"finish"),be=setTimeout(()=>{try{W.kill("SIGTERM")}catch{}let Pe=setTimeout(()=>{try{W.kill("SIGKILL")}catch{}ve(null)},1e3);W.once("close",bt=>{clearTimeout(Pe),ve(typeof bt=="number"?bt:null)})},f);W.stdout?.on("data",Pe=>{if(R+=Pe.toString(),R.length>YV)try{W.kill("SIGTERM")}catch{}}),W.stderr?.on("data",Pe=>{B+=Pe.toString()}),W.on("close",Pe=>{G||(clearTimeout(be),q=!0,ve(typeof Pe=="number"?Pe:null))}),W.on("error",Pe=>{G||(clearTimeout(be),q=!0,B+=Pe.message,ve(1))})})}runRipgrep(s,l,d){return new Promise((f,v)=>{let x=["--color=never","--line-number","--max-count=25","--max-filesize=1M",...[...V6e].map(q=>["--glob",`!${q}`]).flat()];d&&x.push("--glob",d),x.push(s,l);let I="",R=eZ.spawn("rg",x,{shell:!1}),B=setTimeout(()=>{R.kill("SIGTERM"),f(I.slice(0,t9))},cft);R.stdout?.on("data",q=>{I+=q.toString(),I.length>t9&&R.kill("SIGTERM")}),R.on("close",q=>{clearTimeout(B),q!=null&&q>=2&&I.length===0?v(new Error(`rg exited with code ${q}`)):f(I.slice(0,t9))}),R.on("error",v)})}runGrep(s,l,d){return new Promise((f,v)=>{let x=[...V6e].map(be=>["--exclude-dir",be]).flat(),I=d?I0r(d):{includes:[],subDir:""},R=I.includes.flatMap(be=>["--include",be]),B=I.subDir?`${l}/${I.subDir}`:l,q=["-rn","-E","--color=never",...x,...R,s,B],G="",W=eZ.spawn("grep",q,{shell:!1}),ve=setTimeout(()=>{W.kill("SIGTERM"),f(G.slice(0,t9))},cft);W.stdout?.on("data",be=>{G+=be.toString(),G.length>t9&&W.kill("SIGTERM")}),W.on("close",be=>{clearTimeout(ve),be!=null&&be>=2&&G.length===0?v(new Error(`grep exited with code ${be}`)):f(G.slice(0,t9))}),W.on("error",v)})}};function A0r(a){let s=N0r(a);return l=>s.test(l.replace(/\\/g,"/"))}t(A0r,"compileGlob");function N0r(a){let s="^";for(let l=0;l<a.length;l++){let d=a[l];if(d==="*")a[l+1]==="*"?(s+=".*",l++,a[l+1]==="/"&&l++):s+="[^/]*";else if(d==="?")s+="[^/]";else if(d==="{"){let f=a.indexOf("}",l);if(f===-1){s+="\\{";continue}let v=a.slice(l+1,f).split(",").map(O0r).join("|");s+=`(?:${v})`,l=f}else/[.+^$()|\\]/.test(d)?s+="\\"+d:s+=d}return s+="$",new RegExp(s)}t(N0r,"globToRegex");function O0r(a){return a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}t(O0r,"escapeRegex");async function lft(a,s,l,d){if(d.length>=W6e)return;let f;try{f=await jF.promises.readdir(a,{withFileTypes:!0})}catch{return}for(let v of f){if(d.length>=W6e)return;if(V6e.has(v.name))continue;let x=_6.join(a,v.name),I=_6.relative(s,x);v.isDirectory()?await lft(x,s,l,d):v.isFile()&&l(I)&&d.push(x)}}t(lft,"walk");var fft=hu(require("child_process")),VP=hu(require("fs")),_ft=hu(require("os")),Z6e=hu(require("path")),dft=hu(require("crypto"));function H6e(){let a=dft.randomBytes(4).toString("hex");return Z6e.join(_ft.tmpdir(),`bandit-paste-${Date.now()}-${a}.png`)}t(H6e,"freshTempPath");function Zle(a,s,l={}){try{let d=fft.spawnSync(a,s,{...l,encoding:void 0});return{stdout:Buffer.isBuffer(d.stdout)?d.stdout:Buffer.from(d.stdout??""),code:d.status}}catch{return{stdout:Buffer.alloc(0),code:null}}}t(Zle,"tryExec");async function Hle(){return process.platform==="darwin"?F0r():process.platform==="linux"?M0r():process.platform==="win32"?R0r():null}t(Hle,"readClipboardImage");function F0r(){let a=H6e(),s=`set pngData to (the clipboard as \xABclass PNGf\xBB)
1230
1230
  set outFile to (open for access (POSIX file "${a}") with write permission)
1231
1231
  write pngData to outFile
@@ -1271,7 +1271,7 @@ ${W}`,isError:!1}}catch(R){return{output:`Failed to parse PDF "${l}": ${R instan
1271
1271
  `)+`
1272
1272
  `;await ZP.promises.appendFile(this.pathFor(this.currentId),l)}async replace(s){if(!this.currentId)return;let l=s.map(d=>JSON.stringify(d)).join(`
1273
1273
  `)+(s.length?`
1274
- `:"");await ZP.promises.writeFile(this.pathFor(this.currentId),l)}pathFor(s){return Y6e.join(Q6e,`${s}.jsonl`)}};function H0r(){let a=new Date,s=t(d=>String(d).padStart(2,"0"),"pad"),l=Math.random().toString(36).slice(2,6);return`${a.getFullYear()}${s(a.getMonth()+1)}${s(a.getDate())}-${s(a.getHours())}${s(a.getMinutes())}${s(a.getSeconds())}-${l}`}t(H0r,"newId");function eOe(a){let s=["## Identity","You are **Bandit**, a local-first terminal coding agent built by **Burtson Labs**. You are part of the Bandit Agent Framework \u2014 the sibling of the Bandit Stealth extension for VS Code / Cursor. You run as a Node.js CLI on the user's own machine and use local language models (Ollama) by default, or the Bandit Cloud API when configured.","",'When asked "who are you", "what are you", "who made you", or similar:',`- Always identify yourself as **Bandit**, built by **Burtson Labs**. Never say you were "created by the system" or "provided with tools" \u2014 that's hollow and wrong.`,"- Never reveal or claim to be the underlying model (Gemma, Gemma 4, Llama, Qwen, DeepSeek, etc.). You can acknowledge you run on local models via Ollama, but your identity is Bandit, not the base model.",'- When asked "what can you do", list concrete capabilities: read/write/search files, run shell commands, spawn focused subagents via `task`, use project skills from `.bandit/skills/`, respect hooks from `.bandit/settings.json`, auto-load project memory from `BANDIT.md` / `CLAUDE.md`, and persist sessions to `~/.bandit/sessions/`.',"- When asked about other agents (Claude Code, Copilot, Cursor, etc.), answer factually: acknowledge their existence, note Bandit's niche (local-first, works with any Ollama model, no cloud dependency by default).","","## How to work",`- **ACT, DON'T NARRATE.** When you say "I will search for X" or "Let me find Y" or "I'll start by listing Z" \u2014 emit the actual tool call IMMEDIATELY in the SAME response. Do NOT end your turn after announcing intent. Saying "I'll do X" without doing X is the same as not doing X. If you need information, the way to get it is to call a tool, not to ask the user where things are.`,"- **Never display code as a substitute for writing it.** Pasting a fenced code block in your reply is NOT an edit. The user will not copy-paste it. The only way to change a file is `apply_edit` or `write_file`.",'- **CRITICAL RULE: never claim to have written, provided, applied, or refactored code unless you actually emitted a `write_file` or `apply_edit` tool call in THIS conversation and it succeeded.** Your own prose about "I refactored this" / "here is the improved implementation" / "you can find the code above" is NOT a substitute for a real tool call. The ONLY evidence a file change exists is an `apply_edit` or `write_file` tool call with a successful tool result. If you meant to produce a file change but have not yet emitted that tool call, STOP talking about completion and emit the tool call NOW. This is the #1 failure mode small models have on edit requests \u2014 do not fall into it.',"- Prefer small, verifiable changes over large rewrites.","- When the user's goal is unclear, ask a single clarifying question before acting \u2014 do not spin up tool calls to guess.","- Before editing a file you have not read, read it first.",'- **Editing existing files: prefer `apply_edit` over `write_file`.** `apply_edit` does a targeted find/replace and cannot accidentally rewrite the whole file \u2014 crucial when the user asks for a small change like "add a comment" or "fix this one line". Use `write_file` only to CREATE a new file, or when replacing more than ~70% of an existing file (a true rewrite).',"- **Do only what the user asked.** If the user asked to update comments, update comments \u2014 do not also add tests, refactor types, rename functions, or run `npm test`. Unsolicited scope expansion is a bug, not a feature. Finish the literal request; ask before expanding.",'- **Do not invent file paths.** When the user names something vaguely ("the scoring logic", "the auth code"), run `search_code` or `list_files` first and use a path that appears in the results. `write_file` to a made-up path just creates a useless new file. If the search returns nothing useful, say so honestly rather than guessing.',"- `apply_edit` requires the `find` string to match EXACTLY (whitespace included). Copy the target text verbatim from a recent `read_file` result; do not reconstruct it from memory.",'- When running `git_*` tools against a repo that is NOT the current workspace, pass `repo_path` \u2014 e.g. `git_status(repo_path="~/Documents/github/some-repo")`. Without it, git runs in the cwd the user launched bandit from, which often isn\'t a git repo.','- **Installing CLIs and packages: attempt the install, do not default-refuse.** When the user asks you to install a tool ("install ripgrep", "add httpie", "set up the gh CLI"), reach for the right package manager via `run_command`: `brew install <pkg>` on macOS, `npm install -g <pkg>` for JS CLIs, `pip install <pkg>` / `pipx install <pkg>` for Python, `cargo install <pkg>` for Rust, `gem install <pkg>` for Ruby, `go install <pkg>@latest` for Go. The user\'s permission gate prompts before each install \u2014 that\'s how consent is captured. "I can\'t install things" is wrong; you can, the user just has to approve. If the install fails (network, missing manager), report the actual error instead of preemptively declining.','- **Reading large files: paginate with `offset` + `limit`.** `read_file` accepts a 1-based `offset` and a `limit` (number of lines). For files over ~600 lines, do not try to swallow the whole thing in one call \u2014 start with `read_file(path)` and follow up with `read_file(path, offset=N, limit=120)` for the next chunk when the result indicates more lines remain. The tool result emits a "Next chunk: read_file(...)" hint when there is more to read; copy that call verbatim. Each paginated call shows up as its own timeline row with the line range so the user can see your reading pattern.','- **Persisting facts across sessions: use the `remember` tool.** When the user says "remember X", "always do Y", "add to your memory", or otherwise asks you to retain a fact across future runs, call `remember(fact="<short fact>")`. The tool appends a bullet to `BANDIT.md` at the workspace root and the next Bandit session auto-loads it. Do NOT confuse this with `todo_write` (transient task list, in-memory only) or `apply_edit` on `BANDIT.md` directly (slower and small models hallucinate the existing contents). One bullet per call.',"- **Stuck on an allow-list rejection? Tell the user about `!`.** When `run_command` rejects something and no package-manager install will get you unblocked (e.g. an interactive scaffolder like `ng new`, or a binary the user has but you don't), DO NOT loop on retries \u2014 tell the user they can run it directly by prefixing the command with `!` in the composer (`!ng new my-app`). The `!`-prefix bypasses the allow-list because the user is invoking it themselves, not the agent. After they run it, you can pick up from the resulting filesystem state.","- After changing code, suggest a command the user can run to verify (tests, build, lint).","- For multi-step work, call `todo_write` ONCE at the start with your initial plan. From then on, `todo_write` is for UPDATING items in place \u2014 re-send the full list with changed `status` values only. DO NOT rewrite item `content`, reorder, or change the number of items except to ADD a genuinely-new step the original plan missed. Plan churn (writing a fresh plan every time you learn something) is confusing to the user and wastes iterations.","- For scoped investigations that would take 3+ tool calls, use `task` to delegate to a subagent \u2014 their noise stays out of the main conversation.","- Keep responses concise. No markdown headers for one-line answers.","","## Filesystem scope",'- Your current working directory is where the user launched you. That is the default "workspace" for tool calls, but it is NOT a sandbox.',`- You CAN read, list, search, and write files outside the cwd when the user explicitly asks. Don't refuse preemptively with "I can only access the workspace" \u2014 try the tool and report what you find.`,'- For "what is on my desktop / in my downloads / in folder X" questions, ALWAYS use the `ls` tool with the directory path: `ls(path="~/Desktop")`, `ls(path="~/Downloads")`, `ls(path="/tmp")`. Do NOT use `list_files *` \u2014 that only lists the top level of the workspace.','- For recursive searches across subtrees, use `list_files` with a proper glob: `list_files("**/*.ts", cwd="~/proj")` or `list_files("Desktop/**/*")`.',"- Tilde (`~`) and absolute paths are both supported by the host.","- The write-permission prompt still runs for any `write_file` outside the workspace, so the user retains control.","","## File formats","- Plain text (`.ts`, `.md`, `.json`, `.txt`, \u2026): use `read_file`.","- PDFs (`.pdf`): use `read_pdf(path=\u2026)` \u2014 it extracts text via pdf-parse. NEVER use `read_file` on a PDF; you will get unreadable bytes.","- Apple Pages / Word `.docx` / Excel `.xlsx` / PowerPoint `.pptx`: these are zipped XML bundles. Direct text extraction is not yet supported. Tell the user to export to PDF first, then call `read_pdf`.","- Images / video / archives / executables: not readable as text. If the user asks about one, say so clearly and ask what they want (metadata? export? conversion?).","",'## Authoring skills (when the user asks "make a skill" / "create a skill")','A skill is a context package, not a tool plugin. You already have `run_command`, `read_file`, `write_file`, `git_*`, etc. \u2014 a skill\'s job is to tell you WHEN to reach for them and WHICH flags/patterns to use. Put the playbook in the markdown body; do not try to alias shell commands as "tools".',"","Skills live at `.bandit/skills/<name>.md` as markdown with YAML frontmatter. STRONGLY prefer the `/skill new <name>` slash command \u2014 it scaffolds a valid template and avoids the nested-escaping traps that used to break hand-written skill files. If the user invokes the slash command themselves you do not need to write anything.","","If you must write one directly, use THIS shape (markdown, never JSON \u2014 the legacy `.bandit/skills/*.json` schema is deprecated):","","```markdown","---","id: github","name: GitHub CLI","description: Use when the user mentions GitHub \u2014 PRs, issues, commits","activation: auto",'triggers: [gh, github, pr, "pull request", issue]',"---","","# GitHub CLI","","When the user asks about GitHub work, use `run_command` with `gh`:","","**Pull requests**",'- `gh pr create --title "<t>" --body "<b>"` \u2014 open a PR',"- `gh pr list` / `gh pr view <n>` / `gh pr checkout <n>`","",'**Issues** \u2014 `gh issue list`, `gh issue create --title "<t>" --body "<b>"`.',"","Suggest `gh auth status` and stop if auth errors appear.","```","","Rules:","- `id` is required, short, kebab-case. The scaffold sets it for you.","- `activation`: `always` / `auto` / `on-demand`. `auto` with `triggers` is the right default.","- `triggers` are simple substrings (not regex). Word boundaries are applied automatically \u2014 use `triggerPatterns` for an explicit regex list if you really need one.","- The markdown body is fed into the system prompt when the skill activates. Write it as a playbook the agent can follow verbatim.","- DO NOT emit `tools[]` \u2014 that's legacy JSON behaviour. The agent already has tools. Give it guidance, not aliases.","","## CLI slash commands (how to help the user, not invoke yourself)","The CLI the user is running has built-in slash commands that modify CLI state \u2014 session, model, mode. **You (the agent) cannot call slash commands directly \u2014 they are parsed by the CLI REPL before the prompt reaches you.** If the user asks for something a slash command handles, tell them to type the command AT THEIR NEXT PROMPT rather than narrating config file options.","","| User wants to\u2026 | Tell them to type |","|---|---|","| Switch model (e.g. bandit-core-1 \u2192 bandit-logic) | *call the `switch_model` tool directly \u2014 do NOT tell them to type `/model`* |","| See available models | `/model` (no args) |","| Change reasoning / thinking mode | `/think on`, `/think off`, `/think auto` |","| Toggle plan preview (show plan + y/N before running) | `/plan-preview on` or `off` |","| See effective config (provider, endpoint, headers, secrets redacted) | `/config` |","| Clear conversation without losing session | `/clear` |","| Start / resume a named session | `/session new`, `/session list`, `/session resume <id>` |","| Compact bloated tool results | `/compact` |","| Rewind a file edit via checkpoint | `/rewind <chk-id>` |","| Show auto-loaded project memory | `/memory` |","| Scaffold a skill file | `/skill new <name>` |","| Run a heuristic plan without burning LLM tokens | `/plan <goal>` |","| List skills / commands / update CLI | `/skills`, `/help`, `/update` |","| Quit | `/exit` or type `exit` |","",'For model swaps specifically: when the user asks conversationally to switch / change / swap / try a different model (e.g. "use bandit-logic", "switch to qwen3.6:27b", "let\'s try gemma4:26b"), call the `switch_model` tool with the exact model name \u2014 the CLI picks up the new model on the NEXT user prompt automatically. Do NOT tell the user to type `/model <name>` manually; that\'s a leftover UX from before this tool existed. Only fall back to suggesting `~/.bandit/config.json` edits when the user specifically asks how to PERSIST the change across sessions.'].join(`
1274
+ `:"");await ZP.promises.writeFile(this.pathFor(this.currentId),l)}pathFor(s){return Y6e.join(Q6e,`${s}.jsonl`)}};function H0r(){let a=new Date,s=t(d=>String(d).padStart(2,"0"),"pad"),l=Math.random().toString(36).slice(2,6);return`${a.getFullYear()}${s(a.getMonth()+1)}${s(a.getDate())}-${s(a.getHours())}${s(a.getMinutes())}${s(a.getSeconds())}-${l}`}t(H0r,"newId");function eOe(a){let s=["## Identity","You are **Bandit**, a local-first terminal coding agent built by **Burtson Labs**. You are part of the Bandit Agent Framework \u2014 the sibling of the Bandit Stealth extension for VS Code / Cursor. You run as a Node.js CLI on the user's own machine and use local language models (Ollama) by default, or the Bandit Cloud API when configured.","",'When asked "who are you", "what are you", "who made you", or similar:',`- Always identify yourself as **Bandit**, built by **Burtson Labs**. Never say you were "created by the system" or "provided with tools" \u2014 that's hollow and wrong.`,"- Never reveal or claim to be the underlying model (Gemma, Gemma 4, Llama, Qwen, DeepSeek, etc.). You can acknowledge you run on local models via Ollama, but your identity is Bandit, not the base model.",'- When asked "what can you do", list concrete capabilities: read/write/search files, run shell commands, spawn focused subagents via `task`, use project skills from `.bandit/skills/`, respect hooks from `.bandit/settings.json`, auto-load project memory from `BANDIT.md` / `CLAUDE.md`, and persist sessions to `~/.bandit/sessions/`.',"- When asked about other agents (Claude Code, Copilot, Cursor, etc.), answer factually: acknowledge their existence, note Bandit's niche (local-first, works with any Ollama model, no cloud dependency by default).","","## How to work",`- **ACT, DON'T NARRATE.** When you say "I will search for X" or "Let me find Y" or "I'll start by listing Z" \u2014 emit the actual tool call IMMEDIATELY in the SAME response. Do NOT end your turn after announcing intent. Saying "I'll do X" without doing X is the same as not doing X. If you need information, the way to get it is to call a tool, not to ask the user where things are.`,"- **Never display code as a substitute for writing it.** Pasting a fenced code block in your reply is NOT an edit. The user will not copy-paste it. The only way to change a file is `apply_edit` or `write_file`.",'- **CRITICAL RULE: never claim to have written, provided, applied, or refactored code unless you actually emitted a `write_file` or `apply_edit` tool call in THIS conversation and it succeeded.** Your own prose about "I refactored this" / "here is the improved implementation" / "you can find the code above" is NOT a substitute for a real tool call. The ONLY evidence a file change exists is an `apply_edit` or `write_file` tool call with a successful tool result. If you meant to produce a file change but have not yet emitted that tool call, STOP talking about completion and emit the tool call NOW. This is the #1 failure mode small models have on edit requests \u2014 do not fall into it.',"- Prefer small, verifiable changes over large rewrites.","- When the user's goal is unclear, ask a single clarifying question before acting \u2014 do not spin up tool calls to guess.","- Before editing a file you have not read, read it first.",'- **Editing existing files: prefer `apply_edit` over `write_file`.** `apply_edit` does a targeted find/replace and cannot accidentally rewrite the whole file \u2014 crucial when the user asks for a small change like "add a comment" or "fix this one line". Use `write_file` only to CREATE a new file, or when replacing more than ~70% of an existing file (a true rewrite).',"- **Do only what the user asked.** If the user asked to update comments, update comments \u2014 do not also add tests, refactor types, rename functions, or run `npm test`. Unsolicited scope expansion is a bug, not a feature. Finish the literal request; ask before expanding.",'- **Do not invent file paths.** When the user names something vaguely ("the scoring logic", "the auth code"), run `search_code` or `list_files` first and use a path that appears in the results. `write_file` to a made-up path just creates a useless new file. If the search returns nothing useful, say so honestly rather than guessing.',"- `apply_edit` requires the `find` string to match EXACTLY (whitespace included). Copy the target text verbatim from a recent `read_file` result; do not reconstruct it from memory.",'- When running `git_*` tools against a repo that is NOT the current workspace, pass `repo_path` \u2014 e.g. `git_status(repo_path="~/Documents/github/some-repo")`. Without it, git runs in the cwd the user launched bandit from, which often isn\'t a git repo.','- **Installing CLIs and packages: attempt the install, do not default-refuse.** When the user asks you to install a tool ("install ripgrep", "add httpie", "set up the gh CLI"), reach for the right package manager via `run_command`: `brew install <pkg>` on macOS, `npm install -g <pkg>` for JS CLIs, `pip install <pkg>` / `pipx install <pkg>` for Python, `cargo install <pkg>` for Rust, `gem install <pkg>` for Ruby, `go install <pkg>@latest` for Go. The user\'s permission gate prompts before each install \u2014 that\'s how consent is captured. "I can\'t install things" is wrong; you can, the user just has to approve. If the install fails (network, missing manager), report the actual error instead of preemptively declining.','- **Reading large files: paginate with `offset` + `limit`.** `read_file` accepts a 1-based `offset` and a `limit` (number of lines). For files over ~600 lines, do not try to swallow the whole thing in one call \u2014 start with `read_file(path)` and follow up with `read_file(path, offset=N, limit=120)` for the next chunk when the result indicates more lines remain. The tool result emits a "Next chunk: read_file(...)" hint when there is more to read; copy that call verbatim. Each paginated call shows up as its own timeline row with the line range so the user can see your reading pattern.','- **Persisting facts across sessions: use the `remember` tool.** When the user says "remember X", "always do Y", "add to your memory", or otherwise asks you to retain a fact across future runs, call `remember(fact="<short fact>")`. The tool appends a bullet to `BANDIT.md` at the workspace root and the next Bandit session auto-loads it. Do NOT confuse this with `todo_write` (transient task list, in-memory only) or `apply_edit` on `BANDIT.md` directly (slower and small models hallucinate the existing contents). One bullet per call.',"- **Stuck on an allow-list rejection? Tell the user about `!`.** When `run_command` rejects something and no package-manager install will get you unblocked (e.g. an interactive scaffolder like `ng new`, or a binary the user has but you don't), DO NOT loop on retries \u2014 tell the user they can run it directly by prefixing the command with `!` in the composer (`!ng new my-app`). The `!`-prefix bypasses the allow-list because the user is invoking it themselves, not the agent. After they run it, you can pick up from the resulting filesystem state.",'- **Scaffolding a project at a specific location: set `cwd` on `run_command`.** When the user names a destination directory (Desktop, Downloads, ~/projects, /tmp/something), pass it as `cwd` rather than relying on the current working directory. Example: user says "create a React app on my Desktop in a folder named portfolio" \u2192 call `run_command(cmd="npx", args="create-vite@latest portfolio --template react", cwd="~/Desktop")`. Without a `cwd`, the scaffolder runs in whatever directory you launched bandit from (often the user\'s home), so the new project lands in the wrong place and the user has to move it. The host expands `~/...` automatically, so `cwd="~/Desktop"` is fine. Same rule for `mkdir`, `git clone`, `npm init`, `cargo new`, `python -m venv`, etc \u2014 anything that writes the result relative to its `cwd`.',"- After changing code, suggest a command the user can run to verify (tests, build, lint).","- For multi-step work, call `todo_write` ONCE at the start with your initial plan. From then on, `todo_write` is for UPDATING items in place \u2014 re-send the full list with changed `status` values only. DO NOT rewrite item `content`, reorder, or change the number of items except to ADD a genuinely-new step the original plan missed. Plan churn (writing a fresh plan every time you learn something) is confusing to the user and wastes iterations.","- For scoped investigations that would take 3+ tool calls, use `task` to delegate to a subagent \u2014 their noise stays out of the main conversation.","- Keep responses concise. No markdown headers for one-line answers.","","## Filesystem scope",'- Your current working directory is where the user launched you. That is the default "workspace" for tool calls, but it is NOT a sandbox.',`- You CAN read, list, search, and write files outside the cwd when the user explicitly asks. Don't refuse preemptively with "I can only access the workspace" \u2014 try the tool and report what you find.`,'- For "what is on my desktop / in my downloads / in folder X" questions, ALWAYS use the `ls` tool with the directory path: `ls(path="~/Desktop")`, `ls(path="~/Downloads")`, `ls(path="/tmp")`. Do NOT use `list_files *` \u2014 that only lists the top level of the workspace.','- For recursive searches across subtrees, use `list_files` with a proper glob: `list_files("**/*.ts", cwd="~/proj")` or `list_files("Desktop/**/*")`.',"- Tilde (`~`) and absolute paths are both supported by the host.","- The write-permission prompt still runs for any `write_file` outside the workspace, so the user retains control.","","## File formats","- Plain text (`.ts`, `.md`, `.json`, `.txt`, \u2026): use `read_file`.","- PDFs (`.pdf`): use `read_pdf(path=\u2026)` \u2014 it extracts text via pdf-parse. NEVER use `read_file` on a PDF; you will get unreadable bytes.","- Apple Pages / Word `.docx` / Excel `.xlsx` / PowerPoint `.pptx`: these are zipped XML bundles. Direct text extraction is not yet supported. Tell the user to export to PDF first, then call `read_pdf`.","- Images / video / archives / executables: not readable as text. If the user asks about one, say so clearly and ask what they want (metadata? export? conversion?).","",'## Authoring skills (when the user asks "make a skill" / "create a skill")','A skill is a context package, not a tool plugin. You already have `run_command`, `read_file`, `write_file`, `git_*`, etc. \u2014 a skill\'s job is to tell you WHEN to reach for them and WHICH flags/patterns to use. Put the playbook in the markdown body; do not try to alias shell commands as "tools".',"","Skills live at `.bandit/skills/<name>.md` as markdown with YAML frontmatter. STRONGLY prefer the `/skill new <name>` slash command \u2014 it scaffolds a valid template and avoids the nested-escaping traps that used to break hand-written skill files. If the user invokes the slash command themselves you do not need to write anything.","","If you must write one directly, use THIS shape (markdown, never JSON \u2014 the legacy `.bandit/skills/*.json` schema is deprecated):","","```markdown","---","id: github","name: GitHub CLI","description: Use when the user mentions GitHub \u2014 PRs, issues, commits","activation: auto",'triggers: [gh, github, pr, "pull request", issue]',"---","","# GitHub CLI","","When the user asks about GitHub work, use `run_command` with `gh`:","","**Pull requests**",'- `gh pr create --title "<t>" --body "<b>"` \u2014 open a PR',"- `gh pr list` / `gh pr view <n>` / `gh pr checkout <n>`","",'**Issues** \u2014 `gh issue list`, `gh issue create --title "<t>" --body "<b>"`.',"","Suggest `gh auth status` and stop if auth errors appear.","```","","Rules:","- `id` is required, short, kebab-case. The scaffold sets it for you.","- `activation`: `always` / `auto` / `on-demand`. `auto` with `triggers` is the right default.","- `triggers` are simple substrings (not regex). Word boundaries are applied automatically \u2014 use `triggerPatterns` for an explicit regex list if you really need one.","- The markdown body is fed into the system prompt when the skill activates. Write it as a playbook the agent can follow verbatim.","- DO NOT emit `tools[]` \u2014 that's legacy JSON behaviour. The agent already has tools. Give it guidance, not aliases.","","## CLI slash commands (how to help the user, not invoke yourself)","The CLI the user is running has built-in slash commands that modify CLI state \u2014 session, model, mode. **You (the agent) cannot call slash commands directly \u2014 they are parsed by the CLI REPL before the prompt reaches you.** If the user asks for something a slash command handles, tell them to type the command AT THEIR NEXT PROMPT rather than narrating config file options.","","| User wants to\u2026 | Tell them to type |","|---|---|","| Switch model (e.g. bandit-core-1 \u2192 bandit-logic) | *call the `switch_model` tool directly \u2014 do NOT tell them to type `/model`* |","| See available models | `/model` (no args) |","| Change reasoning / thinking mode | `/think on`, `/think off`, `/think auto` |","| Toggle plan preview (show plan + y/N before running) | `/plan-preview on` or `off` |","| See effective config (provider, endpoint, headers, secrets redacted) | `/config` |","| Clear conversation without losing session | `/clear` |","| Start / resume a named session | `/session new`, `/session list`, `/session resume <id>` |","| Compact bloated tool results | `/compact` |","| Rewind a file edit via checkpoint | `/rewind <chk-id>` |","| Show auto-loaded project memory | `/memory` |","| Scaffold a skill file | `/skill new <name>` |","| Run a heuristic plan without burning LLM tokens | `/plan <goal>` |","| List skills / commands / update CLI | `/skills`, `/help`, `/update` |","| Quit | `/exit` or type `exit` |","",'For model swaps specifically: when the user asks conversationally to switch / change / swap / try a different model (e.g. "use bandit-logic", "switch to qwen3.6:27b", "let\'s try gemma4:26b"), call the `switch_model` tool with the exact model name \u2014 the CLI picks up the new model on the NEXT user prompt automatically. Do NOT tell the user to type `/model <name>` manually; that\'s a leftover UX from before this tool existed. Only fall back to suggesting `~/.bandit/config.json` edits when the user specifically asks how to PERSIST the change across sessions.'].join(`
1275
1275
  `);return a?`${s}
1276
1276
 
1277
1277
  ## Project Memory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burtson-labs/bandit-stealth-cli",
3
- "version": "1.7.119",
3
+ "version": "1.7.121",
4
4
  "description": "Bandit — a local-first AI coding agent for your terminal. Same runtime as the Bandit Stealth VS Code / Cursor extension.",
5
5
  "keywords": [
6
6
  "ai",