You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Feature request] Adding hooks such as mount and env to Extensions, and expose to the JavaScript API #2291
While Rust has the Extension trait, the JavaScript @everruns/bashkit library lacks this capability, ScriptedTool cannot be registered as a Bash instance.
While the API does provide capabilities for mounting file systems and loading environment variables (via the files, mounts, and env options), these are defined within BashOptions and can only be passed once during construction. As a result, they cannot be broken down into a reusable, publishable extension module.
Suggestion
Expose a TypeScript extension API in @everruns/bashkit.
The extension is an object containing a set of hooks responsible for mounting the file system, loading environment variables, registering commands, and other operations. Bash calls these hooks when the extension is created and each time it is executed.
classSkillExtension{// Initialization hook: mount the file system, load environment variables, and register commandsonInit(ctx: ExtensionInitContext){ctx.mount(“/skills/my-skill”,skillData.fs);// Mount the file systemctx.env(“SKILL_PATH”,“/skills/my-skill”);// Load environment variablesctx.command(...);}// Lifecycle hook: Before each `execute` beginsonBeforeExecute(ctx: ExecContext){// Optional preparation before each execution}}
Expectations
Users should be able to create a skill extension that mounts an external S3 bucket into a virtual Bash environment during initialization, stores the mount path as an environment variable, and then creates a new command skill. This extension can be published for others to use.
Thanks for the detailed writeup — the gap you identified was real, and a first piece of it just landed on main in #2292 (not yet released; it'll be in the next version).
What's available now
env was the missing half. mount was already callable on a live instance, but env was construction-only, so an extension couldn't be a single function — it had to be a config fragment the host merged by hand plus a post-construction apply step. There is now setEnv (JS) / set_env (Python), and both bindings replay host setEnv/mount calls across reset().
So your SkillExtension is now ordinary userland code — no new framework needed:
import{Bash,FileSystem}from"@everruns/bashkit";constSKILL_PATH="/skills/my-skill";// A publishable module: one function, one instance.exportfunctioninstallSkill(bash: Bash,skillData: FileSystem): void{bash.mount(SKILL_PATH,skillData);// mount the filesystembash.setEnv("SKILL_PATH",SKILL_PATH);// load the env varbash.addBuiltin("my-skill",(ctx)=>// register the commandctx.fs.readFile(`${SKILL_PATH}/SKILL.md`),);}
The lifecycle part matters as much as the ergonomics. Previously reset() kept the builtin but dropped the mount and had no env to keep, so an extension came back half-installed. Now all three survive:
fresh: "# my-skill\n/skills/my-skill\n"
after reset: "# my-skill\n# my-skill\n"
after unmount+reset: 1 # unmount retracts; reset doesn't resurrect
Env that a script exported is still discarded by reset() — only host-set values are replayed.
Python is identical (bash.set_env(...), bash.mount(...), bash.add_builtin(...)).
What this deliberately doesn't do
Three parts of your proposal are still open, and I'd like your read on whether they're needed for your use case:
A declarative extensions: [...] / installExtension API. With the above, this is sugar over a for loop. Worth adding if extensions need identity — a name, a version, duplicate detection, ordering guarantees — but not otherwise.
Schema-backed ctx.command().addBuiltin hands the callback raw argv. Flag parsing, --dry-run, help, and discover currently exist only in ScriptedTool, which is a separate class with no mount. If your skill commands want LLM-facing schemas and discovery, that gap is the real one, and it's independent of the extension packaging.
onBeforeExecute. I left this out on purpose: it puts a JS callback on the hot path, and it can't run under executeSync at all (the sync path can't call back into JS). What did you want it for? If it's re-applying state per execution, the replay behavior above may already cover it; if it's per-execution auth or context, that's a different design.
On the S3 example
Worth flagging since it shapes the answer: the Node binding has no JS-implemented filesystem interface. mount takes either a host directory (allowlist-gated) or an in-memory FileSystem, so "mount an S3 bucket" today means fetching objects into a FileSystem eagerly rather than lazily on read. If lazy/streaming access to a remote bucket is what you actually need, that's a separate feature (a JS-side FileSystem interface) and worth its own issue — let us know and we'll track it.
Does the above cover your use case, or is one of (1)-(3) blocking for you?
Thanks for the reply. After reading more of the BashKit source, I noticed a related SDK capability gap.
ScriptedTool is available in the Rust, Python, and TypeScript SDKs. However, in the TypeScript SDK, ScriptedTool can only be created and executed as a standalone tool; it cannot be registered into an existing Bash instance.
Currently, TypeScript Bash.addBuiltin() only accepts callbacks that receive raw argv. It does not provide the schema, help, discover, or dry-run behavior available for commands registered through ScriptedTool.
I also noticed that the Rust core already has ToolDefExtension, which can install a group of ToolDef/callback-backed commands into a regular Bash instance. This capability does not appear to be exposed through the Python and TypeScript bindings yet.
Regarding the Extension itself, I think BashKit could provide a unified and reusable plugin abstraction on top of these lower-level capabilities.
A standalone installer function is workable for a single integration, but a first-class Extension API could provide npm packages with a consistent shape, identity, configuration model, lifecycle, composition model, command-conflict handling, and uninstall semantics. This would allow third parties to package their services as BashKit extensions, publish them to npm, and let other applications install and use them directly.
For example:
A Agent Skills Extension could configure a skill source, set related environment variables, and register commands such as skills add.
A Agent Memory Extension could integrate an external memory or database service and register commands such as memory list
I am not asking the Extension API itself to immediately support lazy S3 or remote-database filesystem access. The current JS binding does not appear to expose an interface that allows JavaScript to implement a FileSystem.
My proposed Extension API would be the standard packaging and lifecycle layer above the existing runtime primitives, so third-party integrations would not need to reinvent installation, naming, configuration, ordering, and cleanup conventions for every host application.
I am not sure about Extension API. Baskit is more an lower level runtime. If your goal to have extensible system, maybe you can builad a package that would add extension system.
Also about scripted_tool, I am actually thinking to remove it from core Bashkit. scripted_tool is too opionated, and Bahskit should be more like enabler for different versions of codemode implementations. I would probably extract it to own package and maintain externally.
Activity
feat(bindings): host env and mounts applied after construction surviv…
chaliy commentedon Aug 13, 2026
Thanks for the detailed writeup — the gap you identified was real, and a first piece of it just landed on
mainin #2292 (not yet released; it'll be in the next version).What's available now
envwas the missing half.mountwas already callable on a live instance, butenvwas construction-only, so an extension couldn't be a single function — it had to be a config fragment the host merged by hand plus a post-construction apply step. There is nowsetEnv(JS) /set_env(Python), and both bindings replay hostsetEnv/mountcalls acrossreset().So your
SkillExtensionis now ordinary userland code — no new framework needed:And composing several is just a loop, which is what
extensions: [...]would have desugared to anyway:The lifecycle part matters as much as the ergonomics. Previously
reset()kept the builtin but dropped the mount and had no env to keep, so an extension came back half-installed. Now all three survive:Env that a script exported is still discarded by
reset()— only host-set values are replayed.Python is identical (
bash.set_env(...),bash.mount(...),bash.add_builtin(...)).What this deliberately doesn't do
Three parts of your proposal are still open, and I'd like your read on whether they're needed for your use case:
extensions: [...]/installExtensionAPI. With the above, this is sugar over aforloop. Worth adding if extensions need identity — a name, a version, duplicate detection, ordering guarantees — but not otherwise.ctx.command().addBuiltinhands the callback rawargv. Flag parsing,--dry-run,help, anddiscovercurrently exist only inScriptedTool, which is a separate class with nomount. If your skill commands want LLM-facing schemas and discovery, that gap is the real one, and it's independent of the extension packaging.onBeforeExecute. I left this out on purpose: it puts a JS callback on the hot path, and it can't run underexecuteSyncat all (the sync path can't call back into JS). What did you want it for? If it's re-applying state per execution, the replay behavior above may already cover it; if it's per-execution auth or context, that's a different design.On the S3 example
Worth flagging since it shapes the answer: the Node binding has no JS-implemented filesystem interface.
mounttakes either a host directory (allowlist-gated) or an in-memoryFileSystem, so "mount an S3 bucket" today means fetching objects into aFileSystemeagerly rather than lazily on read. If lazy/streaming access to a remote bucket is what you actually need, that's a separate feature (a JS-sideFileSysteminterface) and worth its own issue — let us know and we'll track it.Does the above cover your use case, or is one of (1)-(3) blocking for you?
Niapya commentedon Aug 13, 2026
Thanks for the reply. After reading more of the BashKit source, I noticed a related SDK capability gap.
ScriptedTool is available in the Rust, Python, and TypeScript SDKs. However, in the TypeScript SDK, ScriptedTool can only be created and executed as a standalone tool; it cannot be registered into an existing Bash instance.
Currently, TypeScript Bash.addBuiltin() only accepts callbacks that receive raw argv. It does not provide the schema, help, discover, or dry-run behavior available for commands registered through ScriptedTool.
I also noticed that the Rust core already has ToolDefExtension, which can install a group of ToolDef/callback-backed commands into a regular Bash instance. This capability does not appear to be exposed through the Python and TypeScript bindings yet.
Niapya commentedon Aug 13, 2026
Regarding the Extension itself, I think BashKit could provide a unified and reusable plugin abstraction on top of these lower-level capabilities.
A standalone installer function is workable for a single integration, but a first-class Extension API could provide npm packages with a consistent shape, identity, configuration model, lifecycle, composition model, command-conflict handling, and uninstall semantics. This would allow third parties to package their services as BashKit extensions, publish them to npm, and let other applications install and use them directly.
For example:
Agent SkillsExtension could configure a skill source, set related environment variables, and register commands such as skills add.Agent MemoryExtension could integrate an external memory or database service and register commands such as memory listI am not asking the Extension API itself to immediately support lazy S3 or remote-database filesystem access. The current JS binding does not appear to expose an interface that allows JavaScript to implement a FileSystem.
My proposed Extension API would be the standard packaging and lifecycle layer above the existing runtime primitives, so third-party integrations would not need to reinvent installation, naming, configuration, ordering, and cleanup conventions for every host application.
chaliy commentedon Aug 13, 2026
I am not sure about Extension API. Baskit is more an lower level runtime. If your goal to have extensible system, maybe you can builad a package that would add extension system.
Also about scripted_tool, I am actually thinking to remove it from core Bashkit. scripted_tool is too opionated, and Bahskit should be more like enabler for different versions of codemode implementations. I would probably extract it to own package and maintain externally.