@burtson-labs/bandit-stealth-cli 1.7.127 → 1.7.128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/cli.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,14 +58,21 @@ That's it. No API keys. No cloud services. The agent reads your code, searches,
|
|
|
58
58
|
- **Background subagents** — long investigations spawn detached; status bar shows `bg:N running`; you keep talking; synopsis auto-injects when ready (`/tasks` to inspect, drill down, or cancel)
|
|
59
59
|
- **`watch_command`** — run a dev server / `--watch` test runner for a bounded window, agent reacts to what came out
|
|
60
60
|
- **`find_directory`** — cross-repo discovery; ask "open the auth-api repo" and the agent sweeps `~/Documents/GitHub`, `~/GitHub`, `~/Projects`, `~/code`, `~/dev`, `~/repos`, `~/work`, `~/src`, plus the workspace parent — no "where is that repo?" round-trips
|
|
61
|
+
- **MCP both directions** — speaks the Model Context Protocol as a client (`/mcp add github <token>`, `/mcp add slack`, `/mcp add gitlab`, `/mcp add custom <name> <cmd…>`) and as a server (`bandit mcp serve` exposes Bandit's native tool surface over stdio so Claude Desktop / Cursor / Cline / Continue can drive your codebase through it)
|
|
61
62
|
- **Installs CLIs on demand** — ask Bandit to install `ripgrep`, `httpie`, the GitHub CLI, etc. and it picks the right package manager (`brew`, `npm install -g`, `pip install`, `cargo install`, `gem install`, `go install`) and runs it through the permission gate
|
|
63
|
+
- **Interactive scaffolders work** — `create-vite`, `create-react-app`, `ng new`, etc. detect a non-TTY stdin and self-abort. Bandit recognizes the pattern and surfaces a clear *"run this with `!`"* recovery hint so the model doesn't loop on a "command appeared to succeed" misread
|
|
64
|
+
- **Live command output** — `npm install`, `pip install`, `watch_command npm run dev` stream their output to your terminal as it arrives, dimmed, while the spinner keeps animating. No more wondering if a 20-second install is hung
|
|
65
|
+
- **Interrupt + queue** — press **Esc** mid-turn to cancel the agent and clear your queue. Type a follow-up + Enter to queue it (`queued: N · sends after this turn` in the status row). The next turn picks it up automatically
|
|
66
|
+
- **`?` shortcuts overlay** — type `?` at an empty prompt for a live cheatsheet that disappears the moment you backspace it
|
|
67
|
+
- **`!`-prefix shell escape** — `!cmd` runs straight in your shell with full TTY access. First-use confirmation gate; per-call yellow box every time after so you can't miss the bypass. Catastrophic patterns (`rm -rf`, `mkfs`, `dd if=`) blocked even here
|
|
62
68
|
- **Plan execution** — structured multi-step plans for complex refactors
|
|
63
69
|
- **Session persistence** — every REPL session saved as JSONL under `~/.bandit/sessions/` for later resume
|
|
70
|
+
- **`/insights` HTML report** — local-only activity report: tool stats, top-touched files, languages, longest streak, peak day, error patterns, optional AI summary, mailto share
|
|
64
71
|
- **Project memory** — drop a `BANDIT.md` or `CLAUDE.md` at your workspace root and it's auto-loaded into the system prompt
|
|
65
72
|
- **File + image mentions** — `@path` auto-inlines files; images are either sent multimodally or OCR'd locally (Apple Vision / tesseract)
|
|
66
73
|
- **Clipboard paste** — `Ctrl+V` in the REPL pastes an image straight from your clipboard
|
|
67
74
|
- **Hooks** — `PreToolUse` / `PostToolUse` / `Stop` shell hooks via `.bandit/settings.json`
|
|
68
|
-
- **
|
|
75
|
+
- **12 themes** — Stealth Light/Dark, Midnight, Onyx, Charcoal, Dracula, Nord, Tokyo Night, Solarized Dark/Light, Catppuccin Mocha, Sepia. `/theme` to pick
|
|
69
76
|
- **Cross-platform** — macOS, Linux, Windows; Windows `.cmd`/`.bat` shims (npm/npx/pnpm/tsc) resolved correctly
|
|
70
77
|
- **Update-aware** — fire-and-forget npm-registry check at boot; `update vX.Y.Z available` shows in the status bar when a newer CLI is published
|
|
71
78
|
|
package/dist/cli.js
CHANGED
|
@@ -1225,7 +1225,7 @@ ${(()=>{let X=`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((MNr,q1r)=>{q1r.exports={name:"@burtson-labs/bandit-stealth-cli",version:"1.7.
|
|
1228
|
+
</body></html>`;t(B1r,"startLoopbackListener");t(z1r,"openBrowser");t(U1r,"buildDefaultDeviceLabel");t(J1r,"runOAuthSignIn")});var due=Nt((MNr,q1r)=>{q1r.exports={name:"@burtson-labs/bandit-stealth-cli",version:"1.7.128",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 Xm=yu(require("fs")),kOe=yu(require("os")),bm=yu(require("path")),l9=yu(require("readline")),mue=yu(require("child_process")),Zp=yu(BV()),k6=yu(J6e());var $F=yu(require("fs")),q6e=yu(require("os")),m6=yu(require("path")),eZ=yu(require("child_process"));function M2(a){return a==="~"?q6e.homedir():a.startsWith("~/")?m6.join(q6e.homedir(),a.slice(2)):a}t(M2,"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 BF=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(M2(s))}hasFileBeenRead(s){return this._readFiles.has(M2(s))}async readFile(s){return $F.promises.readFile(M2(s),"utf-8")}async writeFile(s,l){let d=M2(s);if(this.options.approveWrite&&!await this.options.approveWrite(d,l))throw new Error(`Write to ${d} rejected by user`);await $F.promises.mkdir(m6.dirname(d),{recursive:!0}),await $F.promises.writeFile(d,l,"utf-8")}async listFiles(s,l){let d=M2(l??this.workspaceRoot),f=A0r(s),v=[];return await lft(d,d,f,v),v.slice(0,W6e).sort()}async listDirectoryEntries(s){let l=M2(s),d=await $F.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 $F.promises.stat(m6.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=M2(l??this.workspaceRoot);return this.runRipgrep(s,f,d).catch(()=>this.runGrep(s,f,d))}async runCommand(s,l,d){let f=l.map(M2),v=d?M2(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),X=process.stdout.isTTY===!0,W=t((he,be)=>{if(!X)return;(be?process.stderr:process.stdout).write("\r\x1B[2K\x1B[2m"+he+"\x1B[0m")},"writeLive");B.stdout?.on("data",he=>{let be=he.toString();I+=be,W(be,!1),I.length>YV&&B.kill("SIGTERM")}),B.stderr?.on("data",he=>{let be=he.toString();R+=be,W(be,!0)}),B.on("close",he=>{clearTimeout(q);let be=I.slice(0,YV);if(he===0&&/Operation cancelled/i.test(be)&&/(create-vite|create-react-app|create-next|create-svelte|create-astro|create-remix|@clack)/i.test(`${s} ${f.join(" ")} ${be}`)){let vt=[s,...f].join(" ");x({stdout:be,stderr:`Interactive scaffolder detected \u2014 \`${s}\` aborted with "Operation cancelled" because Bandit captures stdout/stderr (no TTY on stdin) and modern scaffolders refuse to start without one. Tell the user to run this directly in their shell: \`!${vt}\`. The \`!\`-prefix runs through their terminal with real stdin, so the scaffolder's prompts work. After they finish, you can pick up from the resulting filesystem state. Do NOT retry the same command \u2014 it will loop forever.`,exitCode:1});return}x({stdout:be,stderr:R.slice(0,4*1024),exitCode:he??0})}),B.on("error",he=>{if(clearTimeout(q),he.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:he.message,exitCode:1})})})}async watchCommand(s,l,d,f){let v=l.map(M2),x=d?M2(d):this.workspaceRoot;return new Promise(I=>{let R="",B="",q=!1,X=!1,W=eZ.spawn(s,v,{cwd:x,shell:process.platform==="win32",env:{...process.env}}),he=t(pt=>{X||(X=!0,I({stdout:R.slice(0,YV),stderr:B.slice(0,4*1024),exitCode:pt,endedEarly:q}))},"finish"),be=setTimeout(()=>{try{W.kill("SIGTERM")}catch{}let pt=setTimeout(()=>{try{W.kill("SIGKILL")}catch{}he(null)},1e3);W.once("close",bt=>{clearTimeout(pt),he(typeof bt=="number"?bt:null)})},f),Pe=process.stdout.isTTY===!0,vt=t((pt,bt)=>{if(!Pe)return;(bt?process.stderr:process.stdout).write("\r\x1B[2K\x1B[2m"+pt+"\x1B[0m")},"writeLive");W.stdout?.on("data",pt=>{let bt=pt.toString();if(R+=bt,vt(bt,!1),R.length>YV)try{W.kill("SIGTERM")}catch{}}),W.stderr?.on("data",pt=>{let bt=pt.toString();B+=bt,vt(bt,!0)}),W.on("close",pt=>{X||(clearTimeout(be),q=!0,he(typeof pt=="number"?pt:null))}),W.on("error",pt=>{X||(clearTimeout(be),q=!0,B+=pt.message,he(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],X="",W=eZ.spawn("grep",q,{shell:!1}),he=setTimeout(()=>{W.kill("SIGTERM"),f(X.slice(0,t9))},cft);W.stdout?.on("data",be=>{X+=be.toString(),X.length>t9&&W.kill("SIGTERM")}),W.on("close",be=>{clearTimeout(he),be!=null&&be>=2&&X.length===0?v(new Error(`grep exited with code ${be}`)):f(X.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 $F.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=m6.join(a,v.name),I=m6.relative(s,x);v.isDirectory()?await lft(x,s,l,d):v.isFile()&&l(I)&&d.push(x)}}t(lft,"walk");var fft=yu(require("child_process")),GP=yu(require("fs")),_ft=yu(require("os")),Z6e=yu(require("path")),dft=yu(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
|
package/package.json
CHANGED