Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gentle-git-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/shell": patch
---

Add hidden default Basic auth credentials for shell git tool providers.
20 changes: 19 additions & 1 deletion packages/shell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class MyAgent extends Agent<Env> {

### Git ToolProvider for codemode

`gitTools(workspace)` exposes all git commands inside sandboxed executions. Auth tokens are auto-injected into clone/fetch/pull/push — the LLM never sees secrets.
`gitTools(workspace)` exposes all git commands inside sandboxed executions. Default auth is auto-injected into clone/fetch/pull/push — the LLM never sees secrets.

```ts
import { stateTools } from "@cloudflare/shell/workers";
Expand All @@ -117,6 +117,24 @@ const providers = [
];
```

For Git servers that require Basic auth, pass hidden default credentials:

```ts
const providers = [
resolveProvider(stateTools(this.workspace)),
resolveProvider(
gitTools(this.workspace, {
auth: {
username: "git",
password: this.env.GIT_PASSWORD
}
})
)
];
```

If both `auth` and `token` are configured, `auth` is used. Direct tool-call auth, if supplied, takes precedence over either default.

## Design goals

- Structured state operations instead of shell parsing
Expand Down
7 changes: 6 additions & 1 deletion packages/shell/src/git/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,4 +398,9 @@ export function createGit(filesystem: FileSystem, defaultDir = "/") {
export type Git = ReturnType<typeof createGit>;

// ── ToolProvider for codemode sandboxes ──────────────────────────────
export { gitTools, gitToolsFromFs } from "./provider";
export {
gitTools,
gitToolsFromFs,
type GitAuthOptions,
type GitToolsOptions
} from "./provider";
62 changes: 42 additions & 20 deletions packages/shell/src/git/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
* });
*
* In the sandbox:
* await git.clone({ url: "https://github.com/org/repo", token: "ghp_..." });
* await git.clone({ url: "https://github.com/org/repo" });
* await git.add({ filepath: "." });
* await git.commit({ message: "fix: bug" });
* await git.push({ token: "ghp_..." });
* await git.push();
*/

import type { ToolProvider } from "@cloudflare/codemode";
Expand Down Expand Up @@ -82,9 +82,27 @@ const GIT_COMMAND_NAMES = [
/** Commands that accept a token/username/password for auth. */
const AUTH_COMMANDS = new Set(["clone", "fetch", "pull", "push"]);

function createGitToolProvider(
export interface GitAuthOptions {
username: string;
password: string;
}

interface GitToolProviderAuthOptions {
token?: string;
auth?: GitAuthOptions;
}

function hasExplicitAuth(opts: Record<string, unknown>) {
return (
Object.prototype.hasOwnProperty.call(opts, "token") ||
Object.prototype.hasOwnProperty.call(opts, "username") ||
Object.prototype.hasOwnProperty.call(opts, "password")
);
}

export function createGitToolProvider(
gitInstance: Git,
defaultToken?: string
authOptions: GitToolProviderAuthOptions = {}
): ToolProvider {
const tools: Record<
string,
Expand All @@ -100,14 +118,16 @@ function createGitToolProvider(
execute: (...args: unknown[]) => {
// positionalArgs mode: args may be [] (no args), [{}], or [{ url: ... }]
let opts = (args[0] ?? {}) as Record<string, unknown>;
// Auto-inject token for auth commands if not explicitly set
if (
defaultToken &&
AUTH_COMMANDS.has(cmd) &&
!opts.token &&
!opts.username
) {
opts = { ...opts, token: defaultToken };
if (AUTH_COMMANDS.has(cmd) && !hasExplicitAuth(opts)) {
if (authOptions.auth) {
opts = {
...opts,
username: authOptions.auth.username,
password: authOptions.auth.password
};
} else if (authOptions.token) {
opts = { ...opts, token: authOptions.token };
}
}
return fn.call(gitInstance, opts);
}
Expand All @@ -125,6 +145,8 @@ function createGitToolProvider(
export interface GitToolsOptions {
/** Default directory for git operations. */
dir?: string;
/** Default basic auth credentials — auto-injected into clone/fetch/pull/push. */
auth?: GitAuthOptions;
/** Default auth token — auto-injected into clone/fetch/pull/push. */
token?: string;
}
Expand All @@ -135,19 +157,19 @@ export function gitTools(
options?: GitToolsOptions
): ToolProvider {
const fs = new WorkspaceFileSystem(workspace);
return createGitToolProvider(
createGit(fs, options?.dir ?? "/"),
options?.token
);
return createGitToolProvider(createGit(fs, options?.dir ?? "/"), {
token: options?.token,
auth: options?.auth
});
}

/** Create a git ToolProvider from a raw FileSystem. */
export function gitToolsFromFs(
filesystem: FileSystem,
options?: GitToolsOptions
): ToolProvider {
return createGitToolProvider(
createGit(filesystem, options?.dir ?? "/"),
options?.token
);
return createGitToolProvider(createGit(filesystem, options?.dir ?? "/"), {
token: options?.token,
auth: options?.auth
});
}
Loading
Loading