diff --git a/CLI-DOCS.md b/CLI-DOCS.md
new file mode 100644
index 0000000..1f667b4
--- /dev/null
+++ b/CLI-DOCS.md
@@ -0,0 +1,164 @@
+# CLI Output Management System
+
+This system automatically captures and caches CLI command outputs in MDX files, converting them to SVG format for consistent rendering.
+
+## Setup
+
+1. Configure `ansi-run.json` with your example project path and starting commit hash:
+
+```json
+{
+ "exampleProjectPath": "/path/to/example/project",
+ "startingHash": "initial-commit-hash",
+ "config": {
+ "cacheDir": "content/cache",
+ "outputFormat": "svg"
+ }
+}
+```
+
+2. Install dependencies:
+```bash
+pnpm install
+```
+
+## Usage
+
+### Adding CLI Commands to MDX
+
+Use special `cli` code blocks in your MDX files:
+
+```mdx
+Here's how to check status:
+
+```cli
+but status
+```
+
+The output will be automatically captured and cached.
+```
+
+### Restore Commands
+
+To restore to a specific state before running commands, add a restore comment:
+
+```mdx
+{/* restore [commit-hash] */}
+
+```cli
+but status
+```
+
+This will run `but restore [commit-hash]` before executing the cli command.
+```
+
+### Updating CLI Outputs
+
+Run the update script to process all MDX files and update CLI outputs:
+
+```bash
+pnpm update-cli
+```
+
+This will:
+- Read your `ansi-run.json` configuration
+- Change to your example project directory
+- Restore to the starting hash
+- Process all MDX files in `content/docs/`
+- Execute CLI commands and capture outputs
+- Convert outputs to SVG using ansi2html
+- Cache outputs in `content/cache/[hash].svg`
+- Update MDX files with hash references: ```cli [hash]
+- Report any changes detected
+
+### How It Works
+
+1. **Processing**: The script finds all ````cli` blocks in MDX files
+2. **Execution**: Commands are run in your configured example project
+3. **Caching**: Output is converted to SVG and stored with a content hash
+4. **Updates**: MDX blocks are updated with hash references
+5. **Rendering**: The CliBlock component renders cached SVGs or shows placeholders
+
+### File Structure
+
+```
+├── ansi-run.json # Configuration
+├── content/
+│ ├── cache/ # Cached SVG outputs
+│ │ ├── abc123def456.svg
+│ │ └── def789ghi012.svg
+│ └── docs/ # MDX documentation files
+│ └── commands/
+│ └── status.mdx
+├── scripts/
+│ └── update-cli-outputs.js # Main processing script
+└── app/
+ └── components/
+ ├── CliBlock.tsx # Rendering component
+ └── remark-cli.ts # MDX transformer
+```
+
+### Example Workflow
+
+1. Create a new MDX file with CLI commands:
+```mdx
+# Status Command
+
+Check your workspace status:
+
+```cli
+but status
+```
+```
+
+2. Run the update script:
+```bash
+pnpm update-cli
+```
+
+3. The script will show output like:
+```
+Processing: content/docs/commands/status.mdx
+Found CLI command: but status
+New CLI block found: but status
+Updated: content/docs/commands/status.mdx
+```
+
+4. Your MDX file is now updated:
+```mdx
+# Status Command
+
+Check your workspace status:
+
+```cli [abc123def456]
+but status
+```
+```
+
+5. When rendered, users see the actual command output in SVG format.
+
+## Troubleshooting
+
+- **Missing outputs**: Run `pnpm update-cli` to generate missing cache files
+- **Outdated outputs**: The script will detect hash changes and notify you
+- **Command failures**: Failed commands will still be cached to show error output
+- **Path issues**: Ensure your `ansi-run.json` paths are absolute and correct
+
+### Updating CLI Outputs
+
+Run the update script to process all MDX files and update CLI outputs:
+
+```bash
+export CLICOLOR_FORCE=1
+export GIT_AUTHOR_DATE="2020-09-09 09:06:03 +0800"
+export GIT_COMMITTER_DATE="2020-10-09 09:06:03 +0800"
+pnpm update-cli
+```
+
+## Commands
+
+The command "man pages" are copied from `../gitbutler/cli-docs` so that changes to the commands docs can be included in changes with the code.
+
+To update the command man-pages, you can run ./scripts/sync-commands.sh
+
+
diff --git a/app/(docs)/[[...slug]]/page.tsx b/app/(docs)/[[...slug]]/page.tsx
index 5d509ae..9955b5e 100644
--- a/app/(docs)/[[...slug]]/page.tsx
+++ b/app/(docs)/[[...slug]]/page.tsx
@@ -8,6 +8,7 @@ import { Callout } from "fumadocs-ui/components/callout"
import { TypeTable } from "fumadocs-ui/components/type-table"
import { Accordion, Accordions } from "fumadocs-ui/components/accordion"
import ImageSection from "@/app/components/ImageSection"
+import CliBlock from "@/app/components/CliBlock"
import type { ComponentProps, FC } from "react"
interface Param {
@@ -103,6 +104,7 @@ export default async function Page(props: { params: Promise }): Promise>,
APIPage: openapi.APIPage
}}
diff --git a/app/components/CliBlock.tsx b/app/components/CliBlock.tsx
new file mode 100644
index 0000000..a01ebe4
--- /dev/null
+++ b/app/components/CliBlock.tsx
@@ -0,0 +1,63 @@
+import fs from 'fs/promises';
+import path from 'path';
+
+interface CliBlockProps {
+ hash?: string;
+ height?: string;
+ lang?: 'cli' | 'ansi';
+ children: React.ReactNode;
+}
+
+export default async function CliBlock({ hash, height, lang = 'cli', children }: CliBlockProps) {
+ // If no hash, render as regular code block
+ if (!hash) {
+ return (
+
+
$ {children}
+
+ Run pnpm update-cli to generate output
+
+
+ );
+ }
+
+ // Determine which directory to use based on lang
+ const dir = lang === 'ansi'
+ ? path.join(process.cwd(), 'public/cli-examples')
+ : path.join(process.cwd(), 'public/cache/cli-output');
+ const htmlPath = path.join(dir, `${hash}.html`);
+
+ try {
+ // Read the HTML file content
+ const fullHtmlContent = await fs.readFile(htmlPath, 'utf8');
+
+ // Extract content from body tag if present (for full HTML documents)
+ const bodyMatch = fullHtmlContent.match(/]*>([\s\S]*)<\/body>/i);
+ const htmlContent = bodyMatch ? bodyMatch[1] : fullHtmlContent;
+
+ return (
+
+
+
$ {children}
+
+
+
+
+
+ );
+ } catch (error) {
+ // HTML file not found, show placeholder
+ const errorMessage = lang === 'ansi'
+ ? 'Output not found. Run ./scripts/sync-commands.sh to sync examples.'
+ : 'Output cache not found. Run pnpm update-cli to generate.';
+
+ return (
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/components/ResizableIframe.tsx b/app/components/ResizableIframe.tsx
new file mode 100644
index 0000000..fbe8df6
--- /dev/null
+++ b/app/components/ResizableIframe.tsx
@@ -0,0 +1,22 @@
+interface ResizableIframeProps {
+ src: string
+ title: string
+ sandbox: string
+ className?: string
+ fixedHeight?: string
+}
+
+export default function ResizableIframe({ src, title, sandbox, className, fixedHeight }: ResizableIframeProps) {
+ const height = fixedHeight || '200px'
+ const minHeight = fixedHeight ? undefined : '200px'
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/app/components/remark-cli.ts b/app/components/remark-cli.ts
new file mode 100644
index 0000000..789a550
--- /dev/null
+++ b/app/components/remark-cli.ts
@@ -0,0 +1,69 @@
+import { visit } from 'unist-util-visit';
+import type { Code } from 'mdast';
+import type { Plugin } from 'unified';
+
+export const remarkCli: Plugin = () => {
+ return (tree) => {
+ visit(tree, 'code', (node: Code) => {
+ // Handle both 'cli' and 'ansi' code blocks
+ if (node.lang === 'cli' || node.lang === 'ansi') {
+ const meta = node.meta || '';
+ let hash: string | undefined;
+ let height: string | undefined;
+
+ if (node.lang === 'ansi') {
+ // For ansi blocks, the meta is the hash directly
+ hash = meta.trim() || undefined;
+ } else {
+ // For cli blocks, parse the old format [hash, height]
+ const paramsMatch = meta.match(/\[([^\]]+)\]/);
+ if (paramsMatch) {
+ const params = paramsMatch[1].split(',').map(p => p.trim());
+ hash = params[0] || undefined;
+ height = params[1] || undefined;
+ }
+ }
+
+ // Build attributes array
+ const attributes = [];
+
+ // Always pass the lang attribute
+ attributes.push({
+ type: 'mdxJsxAttribute',
+ name: 'lang',
+ value: node.lang
+ });
+
+ if (hash) {
+ attributes.push({
+ type: 'mdxJsxAttribute',
+ name: 'hash',
+ value: hash
+ });
+ }
+ if (height) {
+ attributes.push({
+ type: 'mdxJsxAttribute',
+ name: 'height',
+ value: height
+ });
+ }
+
+ // Transform to JSX component
+ const componentNode = {
+ type: 'mdxJsxFlowElement',
+ name: 'CliBlock',
+ attributes,
+ children: [
+ {
+ type: 'text',
+ value: node.value
+ }
+ ]
+ };
+
+ Object.assign(node, componentNode);
+ }
+ });
+ };
+};
\ No newline at end of file
diff --git a/app/global.css b/app/global.css
index d73c1bc..716b4e9 100644
--- a/app/global.css
+++ b/app/global.css
@@ -2,6 +2,9 @@
@tailwind components;
@tailwind utilities;
+/* Import JetBrains Mono Nerd Font */
+@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');
+
@import "open-props/easings";
@import "open-props/animations";
@@ -62,3 +65,17 @@ iframe[src*="youtube"] {
.font-accent {
font-family: var(--font-accent);
}
+
+/* Override fumadocs description paragraph margin */
+p.mb-8.text-lg.text-fd-muted-foreground,
+p.mb-8[class*="text-fd-muted-foreground"],
+p[class*="mb-8"][class*="text-lg"][class*="text-fd-muted-foreground"] {
+ @apply mb-4 !important;
+}
+
+/* CLI output styling - remove vertical padding from pre tags */
+.cli-output-container pre {
+ margin: 0;
+ padding: 0;
+ line-height: 22px;
+}
diff --git a/cli-examples-run.json b/cli-examples-run.json
new file mode 100644
index 0000000..36d3abf
--- /dev/null
+++ b/cli-examples-run.json
@@ -0,0 +1,10 @@
+{
+ "exampleProjectPath": "/Users/schacon/projects/why",
+ "startingHash": "f0f437258043",
+ "ansi_senor_path": "/Users/schacon/.cargo/bin/ansi-senor",
+ "but_path": "/usr/local/bin/but",
+ "config": {
+ "cacheDir": "public/cache/cli-output",
+ "outputFormat": "html"
+ }
+}
diff --git a/content/docs/cli-guides/cli-tutorial/branching-and-commiting.mdx b/content/docs/cli-guides/cli-tutorial/branching-and-commiting.mdx
new file mode 100644
index 0000000..1a3ce9d
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/branching-and-commiting.mdx
@@ -0,0 +1,244 @@
+---
+title: Branching and Commiting
+description: Branch and commit and whatnot
+---
+
+Now that your project is setup and GitButler is installed and configured, you can start branching and committing.
+
+## The Simple Flow
+
+Let’s begin with a simple workflow, one that should be familiar to Git users. We will:
+
+- Do some work
+- Create a new branch
+- Commit to that branch
+
+### Status
+
+Let’s begin by seeing what the status of the working directory is by running `but status`. This will tell you a little more than `git status`, it will list:
+
+1. All files in your working directory that differ from your base branch (`origin/main`) the last time you updated it that aren’t assigned to a branch
+2. A list of the active branches that you have and
+ 1. All assigned file changes in each branch
+ 2. All commits in each branch
+
+So it's sort of like a combination of `git status` and a shortlog of what is on your branches that is not on `origin/master`.
+
+It looks something like this:
+
+{/* restore [f0f437258043] */}
+{/* run but rub wu gemfile-fixes */}
+{/* run but rub te feature-bookmarks */}
+
+```cli [bbbd3383f636a9b4, 462px]
+but status
+```
+
+Here we can see three applied branches: `gemfile-fixes` stacked on `feature-bookmarks` and independent `sc-branch-26`. There are also three unassigned files.
+
+### Create a Branch
+
+Let’s look at a very simple case first. Let’s say we’ve just modified some files and don’t have a branch yet. Our status might look like this:
+
+{/* restore [04d62f15beb4] */}
+
+```cli [e9ed409178fbcced, 264px]
+but status
+```
+
+Now let’s say that we want to put those unassigned file changes into a commit on a new branch called `user-bookmarks`.
+
+To do this, you can use the `but branch new ` command.
+
+{/* run git branch -D user-bookmarks */}
+
+```cli [577ab5c5f0035947, 88px]
+but branch new user-bookmarks
+```
+
+Now if you run `but status` you can see your new empty branch:
+
+```cli [f7e1c84e128c0e56, 330px]
+but status
+```
+
+### Commit to a Branch
+
+Now we can commit our unassigned changes to that branch. You can simply assign your changes to the branch first to commit later (we’ll cover that later in [Rubbing](https://www.notion.so/Rubbing-2545a4bfdeac80209d37cd4d629316cc?pvs=21)), but for now let’s keep it simple and just commit them directly using the `but commit` command.
+
+```cli [927c817ed73b68c7, 88px]
+but commit -m 'all the user bookmarks'
+```
+
+If you don’t specify the `-m` commit message, GitButler will try to open an editor with a tempfile where you can write a longer commit message. It will use the `$EDITOR` environment variable if it’s set, or the `core.editor` Git or GitButler config setting, or it will prompt you for a command to run if you’re in an interactive terminal.
+
+Now our status looks like this, with all unassigned files in a new commit on our new branch:
+
+```cli [0000fdfa2b5ec92b, 220px]
+but status
+```
+
+## Stacked and Parallel Branches
+
+Ok, that’s the simple case, pretty straightforward. However, GitButler can also do some pretty cool things that Git either cannot do or struggles with, namely having multiple active branches that you can work on in parallel, and managing stacked branches. That is, both multiple independent and dependent active branches.
+
+### Parallel Branches
+
+Parallel branches is very simple, you can create multiple simultaneously active branches that you can assign and commit changes to in your workspace.
+
+To create a parallel branch, you simply create a new branch the same way we did before. Let’s say that we want to create a `liked-tweets` branch alongside our existing `user-bookmarks`. We simply run the same `but branch new` command again:
+
+{/* run git branch -D liked-tweets */}
+{/* run echo 'test' > app/controllers/likes_controller.rb */}
+{/* run echo 'test' > app/models/like.rb */}
+
+```cli [c197f357f5e62892, 88px]
+but branch new liked-tweets
+```
+
+Now if we run `but status` we can see our previous branch and our new empty branch.
+
+```cli [b5b56bebb89dedc3, 330px]
+but status
+```
+
+We can see our previous branch and the commit we made, our new empty branch and a couple of modified files. Now we can commit the unassigned changes to that branch with `but commit -m "liked tweets changes" liked-tweets`
+
+```cli [bd4f40610e40f1b5, 88px]
+but commit -m "liked tweets changes" liked-tweets
+```
+
+And now we have one commit in each lane.
+
+```cli [1eccbe4db529d72f, 308px]
+but status
+```
+
+Here we specified the entire branch name as the commit target (as there is more than one), but you can also use the two character short code that is next to each one.
+
+If you don’t specify a branch identifier and you have more than one active branch, then GitButler will prompt you for which branch you wish to commit the unassigned changes to.
+
+We can also see which files were modified in each commit with the `--files` or `-f` option to `but status`:
+
+```cli [f3c56e67a1e24971, 484px]
+but status -f
+```
+
+### Stacked Branches
+
+The other way you can create new branches is to make them stacked, that is, one depends on another one and has to be merged in that order.
+
+To create a new stacked branch in GitButler, you can run `but branch new` with a target branch ID. If we go back in time and instead stack our `liked-tweets` branch, we can make it dependent on the `user-bookmarks` branch by providing it as a stacking "anchor" with `-a` option:
+
+{/* run git branch -D liked-tweets-stacked */}
+{/* restore [e32713a1f41c] */}
+
+```cli [85004f4877113a64, 88px]
+but branch new -a user-bookmarks liked-tweets-stacked
+```
+
+```cli [d68eac2e6f9222fb, 330px]
+but status
+```
+
+Now we can commit to our stacked branch.
+
+```cli [de923765534564a6, 88px]
+but commit -m "liked tweets changes" liked-tweets-stacked
+```
+
+```cli [9d97a568b3786b14, 286px]
+but status
+```
+
+Now if you push to a forge, GitButler will set up the reviews (Pull Request or Merge Request) as a stacked request, where `user-bookmarks` has to be merged either before or with `liked-tweets` but they can be reviewed independently.
+
+## Assigning and Committing Changes
+
+The other way to commit to a branch is to explicitly assign changes to it. This is somewhat like running `git add` in Git, where you’re staging some changes for a future commit. However, unlike Git where you have to do this or override it with `-a` or something, the default in GitButler is to commit all changes by default and only leave out unassigned changes with the flag `-o` or `--only`.
+
+### Assigning Changes
+
+So, how do we assign changes to a specific branch and then only commit those changes?
+
+Let’s look at an example `but status` with six modified files and two empty, parallel branches and assign and commit one file to each branch as a separate commit.
+
+{/* restore [d5c7317b0fd4] */}
+
+```cli [24b3331a2def26d5, 396px]
+but status
+```
+
+We will assign each file to a different branch and then see the result. We assign file changes to branches using the `but rub` command, which combines things. We'll go more into all that rubbing can do later.
+
+You can either rub the file identifier that you see next to each file, or all or part of the file path. For example, in this case to identify the `app/models/bookmark.rb` file, you can do any of:
+
+- `xw`
+- `app/models/`
+- `bookmark`
+- `app/models/bookmark.rb`
+
+In order to differentiate a shortcode from a path, a shortcode is exactly 2 characters and a path needs to be at least 3. This is the same pattern matching used for the branch ID too.
+
+So lets rub the bookmark changes into the bookmarks branch:
+
+```cli [f2990f43c97b0a13, 132px]
+but rub xw,ie,rt user-bookmarks
+```
+
+Now let's rub the user changes into the `user-changes` branch:
+
+```cli [a52570b3640e3743, 88px]
+but rub ku user-changes
+```
+
+Now we have some file changes assigned to each branch and still some unassigned changes:
+
+```cli [d82bd6f8faa2d86e, 396px]
+but status
+```
+
+Now, if we want to create a commit in the `user-bookmarks` branch, we can either run `but commit nd` which will create a commit with the files assigned as well as both files that are unassigned, but _not_ the file assigned to the `user-changes` lane.
+
+Or, we can make a commit with _only_ the assigned files in `user-bookmarks` by using the `-o` option to `but commit`.
+
+```cli [79a511563972cc8f, 88px]
+but commit -o -m "liked tweets view" h4
+```
+
+Now if we look at our status we can see a commit on our branch instead of the assigned changes:
+
+```cli [24f5456cca204e8d, 396px]
+but status
+```
+
+Now let's commit all the rest of the changes (assigned and unassigned) to our other branch:
+
+```cli [682139bab4bdc57d, 88px]
+but commit -m 'bookmarks stuff' nd
+```
+
+```cli [f93e374ac2785a90, 308px]
+but status
+```
+
+### Assigning Ranges
+
+If you happen to have a large number of changes, you can also use ranges or lists for rubbing assignment. So for example, if we go back to this status:
+
+{/* restore [6fdd8fb1d547] */}
+{/* run but rub nd 00 */}
+
+```cli [24b3331a2def26d5, 396px]
+but status
+```
+
+Then you can assign the first, third, and fifth file to a branch with:
+
+```cli [22fb3e53f7ffdf99, 132px]
+but rub nx,xw,rt user-bookmarks
+```
+
+```cli [6fad05e228ab9095, 396px]
+but status
+```
diff --git a/content/docs/cli-guides/cli-tutorial/editing-commits.mdx b/content/docs/cli-guides/cli-tutorial/editing-commits.mdx
new file mode 100644
index 0000000..af7c171
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/editing-commits.mdx
@@ -0,0 +1,41 @@
+---
+title: Editing Commits
+description: Editing commit messages and edit mode with GitButler.
+---
+
+While you can rub changes in and out of commits, you can also edit the commit
+message of any commit in your workspace quite easily.
+
+## Editing Commit Messages
+
+You can edit commit messages with the `but describe` command. So if we have this status:
+
+{/* restore [d69fffa7c6eb] */}
+
+```cli [8e0965d4ad557bc8, 242px]
+but status
+```
+
+Then you can edit the message of any commit by running `but describe `, which will open up your editor of choice with the existing commit message and when you exit the editor, replace that message in the commit and rebase everything above it.
+
+The editor would look something like this:
+
+```
+add user changes
+
+# Please enter the commit message for your changes. Lines starting
+# with '#' will be ignored, and an empty message aborts the commit.
+#
+# Changes in this commit:
+# modified: app/models/user.rb
+# modified: config/routes.rb
+#
+~
+~
+~
+~
+```
+
+Pretty simple.
+
+{/* TODO: Edit Mode */}
diff --git a/content/docs/cli-guides/cli-tutorial/initializing-a-repository.mdx b/content/docs/cli-guides/cli-tutorial/initializing-a-repository.mdx
new file mode 100644
index 0000000..59bb4b2
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/initializing-a-repository.mdx
@@ -0,0 +1,26 @@
+---
+title: Initializing a Repository
+description: How to initialize a GitButler workspace in your repository.
+---
+
+If you run any `but` command in a repository that has never been seen by GitButler before, it will automatically run the `but init` command, which will need to have you confirm the upstream base branch (`origin/main` for example).
+
+You can also run `but init` manually to set that up first, or import it as a GitButler project in the GUI.
+
+```
+❯ but init
+Initialized GitButler project from .
+The default target is origin/main
+```
+
+If there is an `origin/HEAD` reference (servers will often set this as a target branch), GitButler will use this.
+
+Otherwise, the `but init` command will try to guess a default base branch by looking for `origin/main` or `origin/master`, as these are very common. If it finds one of these, it will set it and move on. If it does not, it will ask you which remote branch it should use as it’s target base.
+
+A base branch has to be set on every project GitButler operates on. This is important to tell what is or is not considered "integrated".
+
+
+
+Currently a base branch needs to be on a remote. Our workflow currently assumes you are pushing to a forge and merging after review on that branch to the target base. Support for local-only workflows is coming, but not current possible.
+
+
diff --git a/content/docs/cli-guides/cli-tutorial/meta.json b/content/docs/cli-guides/cli-tutorial/meta.json
new file mode 100644
index 0000000..90e0c5f
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "CLI Tutorial",
+ "pages": [
+ "tutorial-overview",
+ "initializing-a-repository",
+ "branching-and-commiting",
+ "rubbing",
+ "editing-commits",
+ "operations-log",
+ "updating-the-base"
+ ]
+}
\ No newline at end of file
diff --git a/content/docs/cli-guides/cli-tutorial/operations-log.mdx b/content/docs/cli-guides/cli-tutorial/operations-log.mdx
new file mode 100644
index 0000000..8688075
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/operations-log.mdx
@@ -0,0 +1,28 @@
+---
+title: Operations Log
+description: Understanding and using GitButler's operations log to track and undo changes.
+---
+
+GitButler maintains a detailed log of all operations, making it easy to track what happened and undo changes when needed.
+
+## Viewing the Operations Log
+
+See all recent GitButler operations:
+
+```cli [61a4ed332fc62e70, 550px]
+but oplog
+```
+
+## Undoing the last operation
+
+Undo the last operation:
+
+```cli [95763a83f458be55, 132px]
+but undo
+```
+
+## Restoring to a previous point
+
+```cli [1de5d82a8ddf7ce1, 220px]
+but restore -f 6fdd8fb1d547
+```
diff --git a/content/docs/cli-guides/cli-tutorial/rubbing.mdx b/content/docs/cli-guides/cli-tutorial/rubbing.mdx
new file mode 100644
index 0000000..5213eab
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/rubbing.mdx
@@ -0,0 +1,182 @@
+---
+title: Rubbing
+description: Learn how to use the rub command to apply changes to branches.
+---
+
+As we saw in the [Branching and Committing](branching-and-commiting) section, the `but rub` command can be used to assign changes to branch lanes.
+
+However, it can be used to do _so much_ more. Rubbing is essentially combining two things. Since there are lots of _things_ in the tool, combining them together can do lots of different operations. Most of them should be fairly intuitive once you understand the concept.
+
+Let’s take a look at what is possible with this very straightforward command.
+
+## Unassigning Changes
+
+We already showed how you can use `rub` to assign a file change or set of changes to a branch for later committing (rubbing a file and a branch), but what if you want to undo that? Move assignments to a different lane or revert them to being unassigned for later?
+
+As you may have noticed in the `but status` output, there is a special identifier `00` which is always the “unassigned” ID. If you rub anything to `00` then it will move it to unassigned.
+
+So given this status:
+
+{/* restore [f0f437258043] */}
+{/* run but rub wu gemfile-fixes */}
+{/* run but rub te feature-bookmarks */}
+
+```cli [de43f2edf09f5070, 462px]
+but status
+```
+
+We can re-unassign the `README.new.md` file with `but rub np 00`. Or, we can re-assign that file to the `sc-branch-26` parallel branch with `but rub np q6`.
+
+## Amending Commits
+
+However, branch assignment is not all we can do with rubbing. We can also use it to move things to and from commits. A common example would be to amend a commit with new work.
+
+Let’s say that we sent commits out for review and got feedback and instead of creating new commits to address the review, we wanted to actually fix up our commits to be better. This is somewhat complicated to do in Git (something something [fixup commit, autosquash](https://blog.gitbutler.com/git-autosquash), etc).
+
+However, with `rub` it’s incredibly simple. Just rub the new changes into the target commit rather than a branch.
+
+Let’s say that we have a branch with some commits in it, we’ve made changes to two files and want to amend two different commits with the new changes.
+
+```cli [de43f2edf09f5070, 462px]
+but status
+```
+
+If we want to update the first commit (`da42d06`) with the `README-es.md` changes and the last commit (`fdbd753`) with the `app/views/bookmarks/index.html.erb` changes, we can run the following two `rub` commands:
+
+```cli [01f7bcfe8175a154, 88px]
+but rub q2 da
+```
+
+```cli [8df04f3225dfabd3, 88px]
+but rub rt fd
+```
+
+```cli [194379d8cf135c2d, 682px]
+but status --files
+```
+
+Notice that the SHAs have changed for those commits. It has rewritten the commits to have the same messages but incorporated the changes you rubbed into those patches.
+
+If you wanted to rub all the unassigned changes into a specific commit, you could also do that by rubbing the unassigned section to a commit, for example `but rub 00 1l` which would take all unassigned changes and amend commit `1l` with them.
+
+## Squashing Commits
+
+File changes are not the only thing that you can rub. You can also rub commits into things. To squash two commits together, you simply rub them together. Let’s take the last status output and squash the two commits in `gemfile-fixes` together:
+
+{/* restore [f2eae624cc2f] */}
+
+```cli [860ebbd1902dc6d4, 88px]
+but rub 21 f6
+```
+
+Now we can see that we only have one commit in our branch:
+
+```cli [50aed1645949b06d, 396px]
+but status
+```
+
+You probably want to [edit the commit message](editing-commits) after this too, since it will simply combine the two commit messages.
+
+## Uncommitting
+
+Let’s say that we want to just _undo_ a commit - that is, pretend that we had not made that commit and instead put the changes back to unassigned status. In this case we would use the special `00` ID that we talked about earlier, just like unassigning changes, we can unassign commits.
+
+So, if we’re back to this status:
+
+{/* restore [f0f437258043] */}
+
+```cli [bbbd3383f636a9b4, 462px]
+but status
+```
+
+And we want to un-commit the first commit (`fdbd753`) as though we had never made it, you can rub to `00`:
+
+```cli [8b9c7d07db297441, 88px]
+but rub fd 00
+```
+
+Now if we look at our status again, we will see that commit removed and those files back in the unassigned status:
+
+```cli [06337d5a688f418c, 462px]
+but status
+```
+
+## Moving Commits
+
+We can also use rubbing to move a commit from one branch to another branch if we have multiple active branches and committed to the wrong one, or otherwise decide that we want to split up independent work.
+
+Let’s say that we have two commits on one branch and one commit on a second parallel branch and want to move one:
+
+{/* restore [f2eae624cc2f] */}
+
+```cli [8350792b8d9d13f6, 418px]
+but status
+```
+
+We can move the “Add Spanish README and bookmarks feature” commit to the `sc-branch-26` branch with `but rub`:
+
+```cli [d80e33b45a9aaf22, 88px]
+but rub 21 q6
+```
+
+Now we can see that the commit has been moved to the top of the `sc-branch-26` branch:
+
+```cli [d5ab704c72c309c6, 418px]
+but status
+```
+
+Notice that the only SHA that changed was the one that moved, since nothing else needed to be rebased. Rubbing a commit to another branch always adds it to the top of that branch.
+
+As you might imagine, you can also simultaneously move and squash by rubbing a commit in one branch on a commit in another branch too.
+
+## Moving Files between Commits
+
+You can also move specific file changes from one commit to another.
+
+To do that, you need identifiers for the files and hunks in an existing commit, which you can get via a `but status -f`, or `but status --files` that tells status to also list commit file IDs.
+
+```cli [2f4817399ff46441, 682px]
+but status -f
+```
+
+So now we can move the changes from one commit to another by rubbing pretty easily. Let’s take the `app/controllers/bookmarks_controller.rb` change and move it down to the "add bookmark model and associations" commit:
+
+```cli [03420f08f4e2df55, 176px]
+but rub pm f5
+```
+
+Now the change is in the previous commit:
+
+```cli [2f4817399ff46441, 682px]
+but status -f
+```
+
+## Splitting Commits
+
+Ok, so now we can be pretty specifc about moving changes around to all these different states. The last thing we’ll cover here is splitting commits, which requires a new command that creates a new empty commit called `but new`.
+
+By default, `but new` will create a new empty commit at the top of the most recently created branch, however you can specify a different branch, or even a specific place within a branch with existing commits.
+
+Let’s say that we’re back to this state:
+
+{/* restore [f0f437258043] */}
+
+```cli [bbbd3383f636a9b4, 462px]
+but status
+```
+
+Now we want to split the "add bookmark model and associations" into two separate commits. The way we do this is to insert a blank commit in between `06` and `f5` and then rub changes into it (then probably edit the commit message).
+
+We can insert a blank commit by running `but new 06` which inserts a blank commit under the specified commit.
+
+```cli [711027350744cca1, 88px]
+but new 06
+```
+
+Now we have a blank commit:
+
+```cli [bfafb0a0ab77bc27, 704px]
+but status -f
+```
+
+Now we can use the previous method of moving file changes from other commits into it, then edit the commit message with `but describe 6w` (for more on the `describe` command, see [Editing Commits](editing-commits), coming up next).
diff --git a/content/docs/cli-guides/cli-tutorial/tutorial-overview.mdx b/content/docs/cli-guides/cli-tutorial/tutorial-overview.mdx
new file mode 100644
index 0000000..bece3e2
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/tutorial-overview.mdx
@@ -0,0 +1,22 @@
+---
+title: Workflow Overview
+description: What is a semi-normal CLI based workflow with GitButler
+---
+
+https://www.youtube.com/watch?v=loavN_hHuEs
+
+Using the GitButler CLI is meant to make a specific common workflow very simple, which is roughly:
+
+- Create a branch
+- Do work on that branch
+- Commit to that branch
+- Optionally, create another branch if you find unrelated work you need to do
+- Work on and commit to that branch
+- Submit a branch for review
+- Create a stacked branch if needed to continue on dependent work
+- Update your base if work has been integrated to remove merged work
+- Rinse and repeat
+
+Additionally, GitButler is very good at editing commits (amending fixup work, squashing, rewording messages, etc), it keeps a simple log of what you've done in case you need to go back in time, it makes collaborating on a branch with others easy, it has great GitHub/Lab integration and more.
+
+Let's walk through some of the things it can do and what a typical day using the GitButler CLI might look like.
diff --git a/content/docs/cli-guides/cli-tutorial/updating-the-base.mdx b/content/docs/cli-guides/cli-tutorial/updating-the-base.mdx
new file mode 100644
index 0000000..dad353e
--- /dev/null
+++ b/content/docs/cli-guides/cli-tutorial/updating-the-base.mdx
@@ -0,0 +1,88 @@
+---
+title: Updating the Base
+description: Learn how to manage and update your base branch in GitButler.
+---
+
+The base branch is the foundation that your feature branches build upon. Keeping it updated and managing it properly is crucial for a smooth workflow.
+
+## Understanding the Base Branch
+
+The base branch is typically your main development branch that acts as the basis
+for all your local branches.
+
+In practice, this is actually the branch on whatever server you're using to collaborate and merge changes into, so generally it's actually `origin/main` or `origin/master`.
+
+When GitButler is first initialized in a project, you are asked to choose a branch to target, as everything in your working directory that doesn't exactly match the tip of this branch is technically a fork of what is considered production. Whatever that target branch looks like when you choose it is set as your 'base'.
+
+When you start working, everything that is different from that base goes into a branch based off of it.
+
+
+
+## Understanding Upstream
+
+When you first set your base branch (ie, `origin/main`), we record the state of the branch at that time.
+
+However, if someone else merges work into that branch while you're working, the base branch moves forward, but the work you're doing is still based off of where it was. We call this 'upstream' work.
+
+
+
+The problem is that now the stuff we're working on is out of date. It may conflict with what is upstream, it may need the work that is upstream, etc. So how do we get our branch up to date?
+
+## Viewing Upstream
+
+You can see what is upstream and if you're out of date by running `but base check`. This will essentially fetch `origin/main` (or whatever your targeted base branch is) and see if there is anything there that is newer than your base.
+
+{/* restore [64dcebb] */}
+{/* run git push -f origin 32a2175758f7f649ed7a030a17fd21213a5e400f:refs/heads/main */}
+
+Let's take a look at what this looks like. Let's say that our project is at this state:
+
+```cli [f53ff654c67d268e, 462px]
+but status
+```
+
+Let's see if we're up to date with `but base check`:
+
+```cli [1df4a75e976dd98d, 462px]
+but base check
+```
+
+This will also check that upstream work against your currently applied branches to see if anything has been integrated (and thus we can remove), anything conflicts with upstream work, or a merge/rebase should work cleanly.
+
+In this example, we can see that `feature-bookmarks` has been integrated (merged) upstream already, `sc-branch-26` is conflicted with upstream work and `gemfile-fixes` can be cleanly rebased or merged.
+
+## Updating the Base
+
+When you feel like you want to get your active branches up to date, you can run `but base update`. This will rebase your branches on top of the new base commit.
+
+
+
+Let's run it in our example.
+
+```cli [9184aa27f8f241fd, 88px]
+but base update
+```
+
+```cli [8ee36a00318dadf8, 396px]
+but status
+```
+
+OK, now we can see that our integrated branch was removed, our `gemfile-fixes` branch was successfully rebased and our `sc-branch-26` work is marked as conflicted. We'll see how to deal with that state in a minute.
diff --git a/content/docs/cli-guides/installation.mdx b/content/docs/cli-guides/installation.mdx
new file mode 100644
index 0000000..c792543
--- /dev/null
+++ b/content/docs/cli-guides/installation.mdx
@@ -0,0 +1,32 @@
+---
+title: Installation and Setup
+description: Installation and setup guide for the GitButler CLI tool.
+---
+
+How to install and setup the GitButler CLI.
+
+## Installing the `but` CLI
+
+Ok, first thing is first, let's get our `but` CLI installed. Currently there are two ways to do this.
+
+### Via the Desktop Client
+
+If you have the desktop client installed, you can go into your global settings and click on the "Install CLI" button in the "general" section.
+
+
+
+### Homebrew
+
+If you're running on a Mac and use Homebrew, you can install GitButler via `brew install gitbutler` and it will install the CLI for you automatically.
+
+## Setup
+
+There isn't currently much setup you can do, but this will change in the near future, such as setting your name and email for commits or the editor to use for your commit messages.
+
+The next step is to initialize an existing Git repository to use GitButler branching and tooling, which you can read about in the next section, the Tutorial.
diff --git a/content/docs/cli-guides/meta.json b/content/docs/cli-guides/meta.json
new file mode 100644
index 0000000..32e778a
--- /dev/null
+++ b/content/docs/cli-guides/meta.json
@@ -0,0 +1,8 @@
+{
+ "title": "Guides",
+ "defaultOpen": false,
+ "pages": [
+ "installation",
+ "cli-tutorial"
+ ]
+}
\ No newline at end of file
diff --git a/content/docs/commands/but-base.mdx b/content/docs/commands/but-base.mdx
new file mode 100644
index 0000000..0d0add9
--- /dev/null
+++ b/content/docs/commands/but-base.mdx
@@ -0,0 +1,51 @@
+---
+title: 'but base'
+description: 'Commands for managing the base branch'
+---
+
+## Usage
+
+```
+but base
+```
+
+## Subcommands
+
+### check
+
+Fetches remotes from the remote and checks the mergeability of the branches in the workspace.
+
+```
+but base check
+```
+
+Shows the status of the base branch including:
+
+- Base branch name
+- Number of upstream commits
+- Recent commits
+- Status of active branches (updatable, integrated, conflicted, etc.)
+
+### update
+
+Updates the workspace (with all applied branches) to include the latest changes from the base branch.
+
+```
+but base update
+```
+
+Integrates upstream changes into your workspace branches, rebasing or deleting branches as appropriate.
+
+## Examples
+
+Check base branch status:
+
+```
+but base check
+```
+
+Update workspace with base branch changes:
+
+```
+but base update
+```
diff --git a/content/docs/commands/but-branch.mdx b/content/docs/commands/but-branch.mdx
new file mode 100644
index 0000000..0f1e231
--- /dev/null
+++ b/content/docs/commands/but-branch.mdx
@@ -0,0 +1,124 @@
+---
+title: 'but branch'
+description: 'Commands for managing branches'
+---
+
+## Usage
+
+```
+but branch
+```
+
+## Subcommands
+
+### new
+
+Creates a new branch in the workspace.
+
+```
+but branch new [OPTIONS] [BRANCH_NAME]
+```
+
+#### Arguments
+
+- `[BRANCH_NAME]` - Name of the new branch (optional, auto-generated if not provided)
+
+#### Options
+
+- `-a, --anchor ` - Anchor point - either a commit ID or branch name to create the new branch from
+
+### delete
+
+Deletes a branch from the workspace.
+
+```
+but branch delete [OPTIONS]
+```
+
+Alias: `-d`
+
+#### Arguments
+
+- `` - Name of the branch to delete (required)
+
+#### Options
+
+- `-f, --force` - Force deletion without confirmation
+
+### list
+
+List the branches in the repository.
+
+```
+but branch list [OPTIONS]
+```
+
+#### Options
+
+- `-l, --local` - Show only local branches
+
+### unapply
+
+Unapply a branch from the workspace.
+
+```
+but branch unapply [OPTIONS]
+```
+
+#### Arguments
+
+- `` - Name of the branch to unapply (required)
+
+#### Options
+
+- `-f, --force` - Force unapply without confirmation
+
+## Examples
+
+Create a new branch with auto-generated name:
+
+```
+but branch new
+```
+
+Create a new branch with a specific name:
+
+```
+but branch new my-feature
+```
+
+Create a new branch from a specific commit:
+
+```
+but branch new my-feature --anchor abc123
+```
+
+Delete a branch with confirmation:
+
+```
+but branch delete my-feature
+```
+
+Force delete a branch without confirmation:
+
+```
+but branch delete my-feature --force
+```
+
+List all branches:
+
+```
+but branch list
+```
+
+List only local branches:
+
+```
+but branch list --local
+```
+
+Unapply a branch from the workspace:
+
+```
+but branch unapply my-feature
+```
diff --git a/content/docs/commands/but-commit.mdx b/content/docs/commands/but-commit.mdx
new file mode 100644
index 0000000..52e9354
--- /dev/null
+++ b/content/docs/commands/but-commit.mdx
@@ -0,0 +1,53 @@
+---
+title: 'but commit'
+description: 'Commit changes to a stack'
+---
+
+## Usage
+
+```
+but commit [OPTIONS]
+```
+
+## Options
+
+### `-m, --message `
+
+Commit message.
+
+- **Type:** String
+- **Required:** Optional
+
+### `--branch `
+
+Branch CLI ID or name to derive the stack to commit to.
+
+- **Type:** String
+- **Required:** Optional
+
+### `-o, --only`
+
+Only commit assigned files, not unassigned files.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Commit with a message:
+
+```
+but commit -m "Add new feature"
+```
+
+Commit to a specific branch:
+
+```
+but commit -m "Fix bug" --branch my-feature-branch
+```
+
+Commit only assigned files:
+
+```
+but commit -m "Update docs" --only
+```
diff --git a/content/docs/commands/but-describe.mdx b/content/docs/commands/but-describe.mdx
new file mode 100644
index 0000000..1e0eaec
--- /dev/null
+++ b/content/docs/commands/but-describe.mdx
@@ -0,0 +1,41 @@
+---
+title: 'but describe'
+description: 'Edit the commit message of a specified commit'
+---
+
+## Usage
+
+```
+but describe
+```
+
+## Alias
+
+This command can also be invoked using the shorter alias `desc`:
+
+```
+but desc
+```
+
+## Arguments
+
+### ``
+
+Commit ID to edit the message for, or branch ID to rename.
+
+- **Type:** String
+- **Required:** Yes
+
+## Examples
+
+Edit a commit message:
+
+```
+but describe abc123
+```
+
+Rename a branch:
+
+```
+but describe my-feature-branch
+```
diff --git a/content/docs/commands/but-forge.mdx b/content/docs/commands/but-forge.mdx
new file mode 100644
index 0000000..ead9925
--- /dev/null
+++ b/content/docs/commands/but-forge.mdx
@@ -0,0 +1,64 @@
+---
+title: 'but forge'
+description: 'Commands for interacting with forges like GitHub, GitLab, etc.'
+---
+
+## Usage
+
+```
+but forge
+```
+
+## Subcommands
+
+### auth
+
+Authenticate with your forge provider (at the moment, only GitHub is supported).
+
+```
+but forge auth
+```
+
+Initiates a device OAuth flow to authenticate with GitHub. You'll be provided with a code and URL to complete the authentication process.
+
+### list-users
+
+List authenticated forge accounts known to GitButler.
+
+```
+but forge list-users
+```
+
+Displays a list of all GitHub usernames that have been authenticated with GitButler.
+
+### forget
+
+Forget a previously authenticated forge account.
+
+```
+but forge forget
+```
+
+#### Arguments
+
+- `` - The username of the forge account to forget (required)
+
+## Examples
+
+Authenticate with GitHub:
+
+```
+but forge auth
+```
+
+List authenticated users:
+
+```
+but forge list-users
+```
+
+Forget a GitHub account:
+
+```
+but forge forget octocat
+```
diff --git a/content/docs/commands/but-init.mdx b/content/docs/commands/but-init.mdx
new file mode 100644
index 0000000..1774913
--- /dev/null
+++ b/content/docs/commands/but-init.mdx
@@ -0,0 +1,33 @@
+---
+title: 'but init'
+description: 'Initialize a GitButler project from a git repository'
+---
+
+## Usage
+
+```
+but init [OPTIONS]
+```
+
+## Options
+
+### `-r, --repo`
+
+Also initializes a git repository in the current directory if one does not exist.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Initialize GitButler in an existing git repository:
+
+```
+but init
+```
+
+Initialize both git and GitButler:
+
+```
+but init --repo
+```
diff --git a/content/docs/commands/but-mark.mdx b/content/docs/commands/but-mark.mdx
new file mode 100644
index 0000000..9dd0597
--- /dev/null
+++ b/content/docs/commands/but-mark.mdx
@@ -0,0 +1,46 @@
+---
+title: 'but mark'
+description: 'Create or remove a rule for auto-assigning or auto-committing'
+---
+
+## Usage
+
+```
+but mark [OPTIONS]
+```
+
+## Arguments
+
+### ``
+
+The target entity that will be marked.
+
+- **Type:** String
+- **Required:** Yes
+
+## Options
+
+### `-d, --delete`
+
+Deletes a mark.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Create a mark:
+
+```
+but mark my-feature-branch
+```
+
+Delete a mark:
+
+```
+but mark my-feature-branch --delete
+```
+
+## Related Commands
+
+- [`but unmark`](but-unmark) - Removes all marks from the workspace
diff --git a/content/docs/commands/but-mcp.mdx b/content/docs/commands/but-mcp.mdx
new file mode 100644
index 0000000..084fb00
--- /dev/null
+++ b/content/docs/commands/but-mcp.mdx
@@ -0,0 +1,38 @@
+---
+title: 'but mcp'
+description: 'Start up the MCP server'
+---
+
+## Usage
+
+```
+but mcp [OPTIONS]
+```
+
+## Description
+
+Starts the Model Context Protocol (MCP) server for GitButler, enabling integration with AI assistants and other tools that support the MCP protocol.
+
+## Options
+
+### `-i, --internal`
+
+Starts the internal MCP server which has more granular tools.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+- **Note:** This option is hidden from standard help output
+
+## Examples
+
+Start the standard MCP server:
+
+```
+but mcp
+```
+
+Start the internal MCP server with more granular tools:
+
+```
+but mcp --internal
+```
diff --git a/content/docs/commands/but-new.mdx b/content/docs/commands/but-new.mdx
new file mode 100644
index 0000000..179faef
--- /dev/null
+++ b/content/docs/commands/but-new.mdx
@@ -0,0 +1,33 @@
+---
+title: 'but new'
+description: 'Insert a blank commit before a specified commit or at the top of a stack'
+---
+
+## Usage
+
+```
+but new
+```
+
+## Arguments
+
+### ``
+
+Commit ID to insert before, or branch ID to insert at top of stack.
+
+- **Type:** String
+- **Required:** Yes
+
+## Examples
+
+Insert a blank commit before a specific commit:
+
+```
+but new abc123
+```
+
+Insert a blank commit at the top of a branch's stack:
+
+```
+but new my-feature-branch
+```
diff --git a/content/docs/commands/but-oplog.mdx b/content/docs/commands/but-oplog.mdx
new file mode 100644
index 0000000..c939af3
--- /dev/null
+++ b/content/docs/commands/but-oplog.mdx
@@ -0,0 +1,43 @@
+---
+title: 'but oplog'
+description: 'Show operation history'
+---
+
+## Usage
+
+```
+but oplog [OPTIONS]
+```
+
+## Description
+
+Displays the operation log (oplog) which tracks all operations performed in your GitButler workspace. This is useful for understanding what changes have been made and for potentially undoing or restoring to previous states.
+
+## Options
+
+### `--since `
+
+Start from this oplog SHA instead of the head.
+
+- **Type:** String
+- **Required:** Optional
+
+## Examples
+
+Show recent operation history:
+
+```
+but oplog
+```
+
+Show operation history from a specific point:
+
+```
+but oplog --since abc123def456
+```
+
+## Related Commands
+
+- [`but undo`](but-undo) - Undo the last operation
+- [`but restore`](but-restore) - Restore to a specific oplog snapshot
+- [`but snapshot`](but-snapshot) - Create an on-demand snapshot
diff --git a/content/docs/commands/but-publish.mdx b/content/docs/commands/but-publish.mdx
new file mode 100644
index 0000000..3dc8b6d
--- /dev/null
+++ b/content/docs/commands/but-publish.mdx
@@ -0,0 +1,64 @@
+---
+title: 'but publish'
+description: 'Publish review requests for active branches in your workspace'
+---
+
+## Usage
+
+```
+but publish [OPTIONS]
+```
+
+## Description
+
+By default, publishes reviews for all active branches in your workspace. You can optionally specify a single branch to publish.
+
+## Options
+
+### `-b, --branch `
+
+Publish reviews only for the specified branch.
+
+- **Type:** String
+- **Required:** Optional
+
+### `-f, --with-force`
+
+Force push even if it's not fast-forward.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+### `-s, --skip-force-push-protection`
+
+Skip force push protection checks.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-r, --run-hooks`
+
+Run pre-push hooks.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+## Examples
+
+Publish all active branches:
+
+```
+but publish
+```
+
+Publish a specific branch:
+
+```
+but publish --branch my-feature
+```
+
+Publish without running hooks:
+
+```
+but publish --run-hooks=false
+```
diff --git a/content/docs/commands/but-push.mdx b/content/docs/commands/but-push.mdx
new file mode 100644
index 0000000..b2b3929
--- /dev/null
+++ b/content/docs/commands/but-push.mdx
@@ -0,0 +1,115 @@
+---
+title: 'but push'
+description: 'Push a branch/stack to remote'
+---
+
+## Usage
+
+```
+but push [OPTIONS]
+```
+
+## Arguments
+
+### ``
+
+Branch name or CLI ID to push.
+
+- **Type:** String
+- **Required:** Yes
+
+## Options
+
+### `-f, --with-force`
+
+Force push even if it's not fast-forward.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+### `-s, --skip-force-push-protection`
+
+Skip force push protection checks.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-r, --run-hooks`
+
+Run pre-push hooks.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+## Gerrit Options
+
+The following options are only available when Gerrit mode is enabled for your repository:
+
+### `-w, --wip`
+
+Mark change as work-in-progress (Gerrit). Mutually exclusive with `--ready`.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-y, --ready`
+
+Mark change as ready for review (Gerrit). This is the default state. Mutually exclusive with `--wip`.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-a, --hashtag, --tag `
+
+Add hashtag(s) to change (Gerrit). Can be used multiple times.
+
+- **Type:** String (repeatable)
+- **Required:** Optional
+
+### `-t, --topic `
+
+Add custom topic to change (Gerrit). At most one topic can be set. Mutually exclusive with `--topic-from-branch`.
+
+- **Type:** String
+- **Required:** Optional
+
+### `--tb, --topic-from-branch`
+
+Use branch name as topic (Gerrit). Mutually exclusive with `--topic`.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-p, --private`
+
+Mark change as private (Gerrit).
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Push a branch:
+
+```
+but push my-feature-branch
+```
+
+Force push without running hooks:
+
+```
+but push my-feature-branch --with-force --run-hooks=false
+```
+
+Push with Gerrit flags (when Gerrit mode is enabled):
+
+```
+but push my-feature-branch --ready --hashtag bug-fix --hashtag security
+```
+
+## Notes
+
+- Gerrit push flags (`--wip`, `--ready`, `--hashtag/--tag`, `--topic`, `--topic-from-branch`, `--private`) can only be used when gerrit_mode is enabled for the repository
+- `--wip` and `--ready` are mutually exclusive. Ready is the default state.
+- `--topic` and `--topic-from-branch` are mutually exclusive. At most one topic can be set.
+- Multiple hashtags can be specified by using `--hashtag` (or `--tag`) multiple times.
diff --git a/content/docs/commands/but-restore.mdx b/content/docs/commands/but-restore.mdx
new file mode 100644
index 0000000..f868fef
--- /dev/null
+++ b/content/docs/commands/but-restore.mdx
@@ -0,0 +1,48 @@
+---
+title: 'but restore'
+description: 'Restore to a specific oplog snapshot'
+---
+
+## Usage
+
+```
+but restore [OPTIONS]
+```
+
+## Arguments
+
+### ``
+
+Oplog SHA to restore to.
+
+- **Type:** String
+- **Required:** Yes
+
+## Options
+
+### `-f, --force`
+
+Skip confirmation prompt.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Restore to a specific snapshot (with confirmation):
+
+```
+but restore abc123def456
+```
+
+Restore without confirmation prompt:
+
+```
+but restore abc123def456 --force
+```
+
+## Related Commands
+
+- [`but oplog`](but-oplog) - Show operation history
+- [`but undo`](but-undo) - Undo the last operation
+- [`but snapshot`](but-snapshot) - Create an on-demand snapshot
diff --git a/content/docs/commands/but-rub.mdx b/content/docs/commands/but-rub.mdx
new file mode 100644
index 0000000..d682a61
--- /dev/null
+++ b/content/docs/commands/but-rub.mdx
@@ -0,0 +1,67 @@
+---
+title: 'but rub'
+description: 'Combine two entities together to perform an operation'
+---
+
+## Usage
+
+```
+but rub
+```
+
+## Description
+
+The `rub` command is a versatile operation that combines two entities together to perform various operations depending on the types of entities provided.
+
+### Supported Operations
+
+| Source | Target | Operation |
+| ------ | ------ | --------- |
+| File | Commit | Amend |
+| Branch | Commit | Amend |
+| Commit | Commit | Squash |
+| File | Branch | Assign |
+| Branch | Branch | Assign |
+| Commit | Branch | Move |
+
+## Arguments
+
+### ``
+
+The source entity to combine.
+
+- **Type:** String
+- **Required:** Yes
+
+### ``
+
+The target entity to combine with the source.
+
+- **Type:** String
+- **Required:** Yes
+
+## Examples
+
+Amend a file to a commit:
+
+```
+but rub path/to/file.txt abc123
+```
+
+Squash two commits:
+
+```
+but rub abc123 def456
+```
+
+Assign a file to a branch:
+
+```
+but rub path/to/file.txt my-feature-branch
+```
+
+Move a commit to a different branch:
+
+```
+but rub abc123 my-other-branch
+```
diff --git a/content/docs/commands/but-snapshot.mdx b/content/docs/commands/but-snapshot.mdx
new file mode 100644
index 0000000..3ac1526
--- /dev/null
+++ b/content/docs/commands/but-snapshot.mdx
@@ -0,0 +1,43 @@
+---
+title: 'but snapshot'
+description: 'Create an on-demand snapshot with optional message'
+---
+
+## Usage
+
+```
+but snapshot [OPTIONS]
+```
+
+## Description
+
+Creates a snapshot of the current workspace state in the operation log. This is useful for creating manual checkpoints before making significant changes.
+
+## Options
+
+### `-m, --message `
+
+Message to include with the snapshot.
+
+- **Type:** String
+- **Required:** Optional
+
+## Examples
+
+Create a snapshot without a message:
+
+```
+but snapshot
+```
+
+Create a snapshot with a descriptive message:
+
+```
+but snapshot -m "Before refactoring authentication module"
+```
+
+## Related Commands
+
+- [`but oplog`](but-oplog) - Show operation history
+- [`but undo`](but-undo) - Undo the last operation
+- [`but restore`](but-restore) - Restore to a specific oplog snapshot
diff --git a/content/docs/commands/but-status.mdx b/content/docs/commands/but-status.mdx
new file mode 100644
index 0000000..8b6d21d
--- /dev/null
+++ b/content/docs/commands/but-status.mdx
@@ -0,0 +1,67 @@
+---
+title: 'but status'
+description: 'Overview of the uncommitted changes in the repository'
+---
+
+## Usage
+
+```
+but status [OPTIONS]
+```
+
+## Alias
+
+This command can also be invoked using the shorter alias `st`:
+
+```
+but st [OPTIONS]
+```
+
+## Example Output
+
+```ansi but-st-c43eb45a
+but status
+```
+
+## Options
+
+### `-f, --files`
+
+Determines whether the committed files should be shown as well.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-v, --verbose`
+
+Show verbose output with commit author and timestamp.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-r, --review`
+
+Show the forge review information.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Show basic status:
+
+```
+but status
+```
+
+Show status with files:
+
+```
+but status --files
+```
+
+Show verbose status with review information:
+
+```
+but status -v -r
+```
diff --git a/content/docs/commands/but-undo.mdx b/content/docs/commands/but-undo.mdx
new file mode 100644
index 0000000..0a7de20
--- /dev/null
+++ b/content/docs/commands/but-undo.mdx
@@ -0,0 +1,32 @@
+---
+title: 'but undo'
+description: 'Undo the last operation by reverting to the previous snapshot'
+---
+
+## Usage
+
+```
+but undo
+```
+
+## Description
+
+Reverts the workspace to the state it was in before the last operation. This uses the operation log (oplog) to roll back changes.
+
+## Options
+
+This command takes no specific options.
+
+## Examples
+
+Undo the last operation:
+
+```
+but undo
+```
+
+## Related Commands
+
+- [`but oplog`](but-oplog) - Show operation history
+- [`but restore`](but-restore) - Restore to a specific oplog snapshot
+- [`but snapshot`](but-snapshot) - Create an on-demand snapshot
diff --git a/content/docs/commands/but-unmark.mdx b/content/docs/commands/but-unmark.mdx
new file mode 100644
index 0000000..d1073b8
--- /dev/null
+++ b/content/docs/commands/but-unmark.mdx
@@ -0,0 +1,30 @@
+---
+title: 'but unmark'
+description: 'Remove all marks from the workspace'
+---
+
+## Usage
+
+```
+but unmark
+```
+
+## Description
+
+This command removes all auto-assign and auto-commit rules (marks) from the workspace.
+
+## Options
+
+This command takes no specific options.
+
+## Examples
+
+Remove all marks:
+
+```
+but unmark
+```
+
+## Related Commands
+
+- [`but mark`](but-mark) - Creates or removes a rule for auto-assigning or auto-committing
diff --git a/content/docs/commands/groups.json b/content/docs/commands/groups.json
new file mode 100644
index 0000000..5c93dbc
--- /dev/null
+++ b/content/docs/commands/groups.json
@@ -0,0 +1,35 @@
+{
+ "Basics": [
+ "but-init"
+ ],
+ "Inspection": [
+ "but-log",
+ "but-status"
+ ],
+ "Committing": [
+ "but-base",
+ "but-branch",
+ "but-rub",
+ "but-commit",
+ "but-mark",
+ "but-unmark"
+ ],
+ "Editing": [
+ "but-describe",
+ "but-new"
+ ],
+ "Forges": [
+ "but-push",
+ "but-publish",
+ "but-forge"
+ ],
+ "Operations Log": [
+ "but-oplog",
+ "but-undo",
+ "but-restore",
+ "but-snapshot"
+ ],
+ "AI Tools": [
+ "but-mcp"
+ ]
+}
diff --git a/content/docs/commands/meta.json b/content/docs/commands/meta.json
new file mode 100644
index 0000000..f6846f4
--- /dev/null
+++ b/content/docs/commands/meta.json
@@ -0,0 +1,31 @@
+{
+ "title": "Commands",
+ "defaultOpen": false,
+ "pages": [
+ "---Basics---",
+ "but-init",
+ "---Inspection---",
+ "but-status",
+ "---Committing---",
+ "but-base",
+ "but-branch",
+ "but-rub",
+ "but-commit",
+ "but-mark",
+ "but-unmark",
+ "---Editing---",
+ "but-describe",
+ "but-new",
+ "---Forges---",
+ "but-push",
+ "but-publish",
+ "but-forge",
+ "---Operations Log---",
+ "but-oplog",
+ "but-undo",
+ "but-restore",
+ "but-snapshot",
+ "---AI Tools---",
+ "but-mcp"
+ ]
+}
diff --git a/content/docs/meta.json b/content/docs/meta.json
index e1b8f48..3308887 100644
--- a/content/docs/meta.json
+++ b/content/docs/meta.json
@@ -6,11 +6,14 @@
"index",
"guide",
"why-gitbutler",
- "---GitButler Client---",
+ "---GitButler Desktop Client---",
"overview",
"butler-flow",
"features",
"troubleshooting",
+ "---GitButler CLI---",
+ "cli-guides",
+ "commands",
"---Development---",
"...development",
"---Other---",
diff --git a/package.json b/package.json
index 3d1380c..da8bfec 100644
--- a/package.json
+++ b/package.json
@@ -7,14 +7,17 @@
"build:llmstxt": "node scripts/generate-llmstxt.js",
"build": "pnpm build:llmstxt && next build",
"dev": "next dev",
- "start": "next start"
+ "start": "next start",
+ "update-cli": "node scripts/update-cli-outputs.js"
},
"dependencies": {
"@fumadocs/mdx-remote": "^0.2.3",
"@gitbutler/design-core": "^1.2.0",
"@radix-ui/react-dialog": "^1.1.2",
"@shikijs/transformers": "^1.22.2",
+ "ansi2": "^0.3.0",
"cmdk": "^1.0.4",
+ "glob": "^10.3.10",
"fumadocs-core": "14.7.7",
"fumadocs-mdx": "11.5.7",
"fumadocs-openapi": "^5.7.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 00cff9a..75ff3d5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
'@shikijs/transformers':
specifier: ^1.22.2
version: 1.22.2
+ ansi2:
+ specifier: ^0.3.0
+ version: 0.3.0
cmdk:
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -38,6 +41,9 @@ importers:
fumadocs-ui:
specifier: 14.7.7
version: 14.7.7(@types/react-dom@18.3.1)(@types/react@18.3.12)(fumadocs-core@14.7.7(@types/react@18.3.12)(next@15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14)
+ glob:
+ specifier: ^10.3.10
+ version: 10.4.2
next:
specifier: ^15.0.3
version: 15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1615,6 +1621,10 @@ packages:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
+ ansi2@0.3.0:
+ resolution: {integrity: sha512-hVWEsuR7GykrNGwghoD4RzWrW+gQqycENr0BZLW7iwNurdAku0nuHg/nPlZwcL4GuqfbEwxUC+ZFSDdrAzT2IQ==}
+ hasBin: true
+
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
@@ -1808,6 +1818,11 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ clipanion@4.0.0-rc.4:
+ resolution: {integrity: sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==}
+ peerDependencies:
+ typanion: '*'
+
clsx@2.0.0:
resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
engines: {node: '>=6'}
@@ -4116,6 +4131,9 @@ packages:
peerDependencies:
typescript: '*'
+ typanion@3.14.0:
+ resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==}
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -5848,6 +5866,11 @@ snapshots:
ansi-styles@6.2.1: {}
+ ansi2@0.3.0:
+ dependencies:
+ clipanion: 4.0.0-rc.4(typanion@3.14.0)
+ typanion: 3.14.0
+
any-promise@1.3.0: {}
anymatch@3.1.3:
@@ -6076,6 +6099,10 @@ snapshots:
client-only@0.0.1: {}
+ clipanion@4.0.0-rc.4(typanion@3.14.0):
+ dependencies:
+ typanion: 3.14.0
+
clsx@2.0.0: {}
clsx@2.1.1: {}
@@ -6611,7 +6638,7 @@ snapshots:
debug: 4.3.7
enhanced-resolve: 5.17.0
eslint: 9.14.0(jiti@1.21.6)
- eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@1.21.6)))(eslint@9.14.0(jiti@1.21.6))
+ eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.14.0(jiti@1.21.6))
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1)(eslint@9.14.0(jiti@1.21.6))
fast-glob: 3.3.2
get-tsconfig: 4.7.5
@@ -6623,7 +6650,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@1.21.6)))(eslint@9.14.0(jiti@1.21.6)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.14.0(jiti@1.21.6)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -6634,7 +6661,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.8.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@1.21.6)))(eslint@9.14.0(jiti@1.21.6)):
+ eslint-module-utils@2.8.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.14.0(jiti@1.21.6)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -6656,7 +6683,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.14.0(jiti@1.21.6)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@1.21.6)))(eslint@9.14.0(jiti@1.21.6))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.14.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.14.0(jiti@1.21.6))
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -6932,7 +6959,7 @@ snapshots:
foreground-child@3.2.1:
dependencies:
- cross-spawn: 7.0.3
+ cross-spawn: 7.0.6
signal-exit: 4.1.0
fraction.js@4.3.7: {}
@@ -9105,6 +9132,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ typanion@3.14.0: {}
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
diff --git a/public/cache/cli-output/0000fdfa2b5ec92b.html b/public/cache/cli-output/0000fdfa2b5ec92b.html
new file mode 100644
index 0000000..b0ba51c
--- /dev/null
+++ b/public/cache/cli-output/0000fdfa2b5ec92b.html
@@ -0,0 +1,30 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊
+┊╭┄nd [user-bookmarks]
+┊● e86dada all the user bookmarks
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/01f7bcfe8175a154.html b/public/cache/cli-output/01f7bcfe8175a154.html
new file mode 100644
index 0000000..3cdbf38
--- /dev/null
+++ b/public/cache/cli-output/01f7bcfe8175a154.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but rub q2 da
+
+
+
+
Amended README-es.md → 42423c4
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/03420f08f4e2df55.html b/public/cache/cli-output/03420f08f4e2df55.html
new file mode 100644
index 0000000..602a75e
--- /dev/null
+++ b/public/cache/cli-output/03420f08f4e2df55.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
Rubbed the wrong way. Source 'pm' not found. If you just performed a Git operation (squash, rebase, etc.), try running 'but status' to refresh the current state.
+Error: Rubbed the wrong way.
+
+Caused by:
+ Source 'pm' not found. If you just performed a Git operation (squash, rebase, etc.), try running 'but status' to refresh the current state.
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/06337d5a688f418c.html b/public/cache/cli-output/06337d5a688f418c.html
new file mode 100644
index 0000000..72c0043
--- /dev/null
+++ b/public/cache/cli-output/06337d5a688f418c.html
@@ -0,0 +1,41 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ wu M README-es.md 🔒 eeaf0ef
+┊ te A README.new.md
+┊ ku M app/models/user.rb
+┊ rt A app/views/bookmarks/index.html.erb
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● eeaf0ef Add Spanish README and bookmarks feature
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● f55a30e add bookmark model and associations
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/194379d8cf135c2d.html b/public/cache/cli-output/194379d8cf135c2d.html
new file mode 100644
index 0000000..01efc1e
--- /dev/null
+++ b/public/cache/cli-output/194379d8cf135c2d.html
@@ -0,0 +1,51 @@
+
+
+
+
+ /usr/local/bin/but status --files
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ te A README.new.md
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● b0f62d6 Add Spanish README and bookmarks feature
+┊│ pc M Gemfile
+┊│ yq A README-es.md
+┊│ gy A app/controllers/bookmarks_controller.rb
+┊│ iy M app/views/dashboard/index.html.erb
+┊● d077078 just the one
+┊│ od M app/models/user.rb
+┊│ v8 A app/views/bookmarks/index.html.erb
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+┊│ wj A app/models/bookmark.rb
+┊│ i0 M config/routes.rb
+┊│ hq A spacer.txt
+┊│ ww A testing.md
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● f55a30e add bookmark model and associations
+┊│ ls M app/models/tweet.rb
+┊│ p0 A db/migrate/20250925000001_create_bookmarks.rb
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/1de5d82a8ddf7ce1.html b/public/cache/cli-output/1de5d82a8ddf7ce1.html
new file mode 100644
index 0000000..c7d1597
--- /dev/null
+++ b/public/cache/cli-output/1de5d82a8ddf7ce1.html
@@ -0,0 +1,30 @@
+
+
+
+
+ /usr/local/bin/but restore -f 6fdd8fb1d547
+
+
+
+
Restoring to oplog snapshot...
+ Target: MoveHunk (2025-10-08 11:37:29)
+ Snapshot: 6fdd8fb
+
+✓ Restore completed successfully!
+
+Workspace has been restored to the selected snapshot.
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/1df4a75e976dd98d.html b/public/cache/cli-output/1df4a75e976dd98d.html
new file mode 100644
index 0000000..f52746d
--- /dev/null
+++ b/public/cache/cli-output/1df4a75e976dd98d.html
@@ -0,0 +1,41 @@
+
+
+
+
+ /usr/local/bin/but base check
+
+
+
+
🔍 Checking base branch status...
+
+📍 Base branch: origin/main
+⏫ Upstream commits: 2 new commits on origin/main
+
+ 204e309 Merge pull request #10 from schacon/sc-description Add user description
+ a76a41a Merge pull request #11 from schacon/sc-branch-11 Add notifications tabl
+ 249c8fd Merge pull request #12 from schacon/sc-branch-10 sc-branch-10
+
+Active Branch Status
+
+🔄 feature-bookmarks (Integrated)
+
+✅ gemfile-fixes (Updatable)
+
+❗️ sc-branch-26 (Conflicted (Not Rebasable))
+
+Run `but base update` to update your branches
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/1eccbe4db529d72f.html b/public/cache/cli-output/1eccbe4db529d72f.html
new file mode 100644
index 0000000..c3ff98f
--- /dev/null
+++ b/public/cache/cli-output/1eccbe4db529d72f.html
@@ -0,0 +1,34 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊
+┊╭┄nd [user-bookmarks]
+┊● e86dada all the user bookmarks
+├╯
+┊
+┊╭┄u1 [liked-tweets]
+┊● 65b17f7 liked tweets changes
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/22fb3e53f7ffdf99.html b/public/cache/cli-output/22fb3e53f7ffdf99.html
new file mode 100644
index 0000000..cdb3a01
--- /dev/null
+++ b/public/cache/cli-output/22fb3e53f7ffdf99.html
@@ -0,0 +1,26 @@
+
+
+
+
+ /usr/local/bin/but rub nx,xw,rt user-bookmarks
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/24b3331a2def26d5.html b/public/cache/cli-output/24b3331a2def26d5.html
new file mode 100644
index 0000000..31c1228
--- /dev/null
+++ b/public/cache/cli-output/24b3331a2def26d5.html
@@ -0,0 +1,38 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ nx M Gemfile
+┊ ie A app/controllers/bookmarks_controller.rb
+┊ xw A app/models/bookmark.rb
+┊ ku M app/models/user.rb
+┊ rt A app/views/bookmarks/index.html.erb
+┊ rv M config/routes.rb
+┊
+┊╭┄nd [user-bookmarks] (no commits)
+├╯
+┊
+┊╭┄h4 [user-changes] (no commits)
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/24f5456cca204e8d.html b/public/cache/cli-output/24f5456cca204e8d.html
new file mode 100644
index 0000000..f4b91b1
--- /dev/null
+++ b/public/cache/cli-output/24f5456cca204e8d.html
@@ -0,0 +1,38 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ nx M Gemfile
+┊ rv M config/routes.rb
+┊
+┊╭┄nd [user-bookmarks] (no commits)
+┊│ md A app/controllers/bookmarks_controller.rb
+┊│ pe A app/models/bookmark.rb
+┊│ v0 A app/views/bookmarks/index.html.erb
+├╯
+┊
+┊╭┄h4 [user-changes]
+┊● 90a2ab2 liked tweets view
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/278d679c366756a7.html b/public/cache/cli-output/278d679c366756a7.html
new file mode 100644
index 0000000..7aa70d9
--- /dev/null
+++ b/public/cache/cli-output/278d679c366756a7.html
@@ -0,0 +1,20 @@
+
'c.
schacon
@
agador.sparticus
,xNMM.
------------------------
.OMMMMo
OS
: macOS 15.7 24G214 arm64
OMMM0,
Host
: Mac15,12
.;loddo:' loolloddol;.
Kernel
: 24.6.0
cKMMMMMMMMMMNWMMMMMMMMMM0:
Uptime
: 44 days, 7 hours, 8 mins
.KMMMMMMMMMMMMMMMMMMMMMMMWd.
Packages
: 201 (brew)
XMMMMMMMMMMMMMMMMMMMMMMMX.
Shell
: zsh 5.9
;MMMMMMMMMMMMMMMMMMMMMMMM:
Resolution
: 2560x1440
:MMMMMMMMMMMMMMMMMMMMMMMM:
DE
: Aqua
.MMMMMMMMMMMMMMMMMMMMMMMMX.
WM
: Quartz Compositor
kMMMMMMMMMMMMMMMMMMMMMMMMWd.
WM Theme
: Blue (Light)
.XMMMMMMMMMMMMMMMMMMMMMMMMMMk
Terminal
: ghostty
.XMMMMMMMMMMMMMMMMMMMMMMMMK.
CPU
: Apple M3
kMMMMMMMMMMMMMMMMMMMMMMd
GPU
: Apple M3
;KMMMMMMMWXXWMMMMMMMk.
Memory
: 4177MiB / 24576MiB
.cooc,. .,coo:.
+
\ No newline at end of file
diff --git a/public/cache/cli-output/2f4817399ff46441.html b/public/cache/cli-output/2f4817399ff46441.html
new file mode 100644
index 0000000..036b0ab
--- /dev/null
+++ b/public/cache/cli-output/2f4817399ff46441.html
@@ -0,0 +1,51 @@
+
+
+
+
+ /usr/local/bin/but status -f
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ te A README.new.md
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● f6532f9 just the one
+┊│ p5 M app/models/user.rb
+┊│ oq A app/views/bookmarks/index.html.erb
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+┊│ wj A app/models/bookmark.rb
+┊│ i0 M config/routes.rb
+┊│ hq A spacer.txt
+┊│ ww A testing.md
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● 221a4a4 Add Spanish README and bookmarks feature
+┊│ wk M Gemfile
+┊│ j8 A README-es.md
+┊│ vo A app/controllers/bookmarks_controller.rb
+┊│ j8 M app/views/dashboard/index.html.erb
+┊● f55a30e add bookmark model and associations
+┊│ ls M app/models/tweet.rb
+┊│ p0 A db/migrate/20250925000001_create_bookmarks.rb
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/500825cb80f612de.html b/public/cache/cli-output/500825cb80f612de.html
new file mode 100644
index 0000000..d71b0ff
--- /dev/null
+++ b/public/cache/cli-output/500825cb80f612de.html
@@ -0,0 +1,730 @@
+
+
+
+
+ /usr/local/bin/but -j status
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/682139bab4bdc57d.html b/public/cache/cli-output/682139bab4bdc57d.html
new file mode 100644
index 0000000..6843750
--- /dev/null
+++ b/public/cache/cli-output/682139bab4bdc57d.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but commit -m bookmarks stuff nd
+
+
+
+
Created commit f35a14a on branch user-bookmarks
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/69330060f4f79ec5.html b/public/cache/cli-output/69330060f4f79ec5.html
new file mode 100644
index 0000000..cbadbaf
--- /dev/null
+++ b/public/cache/cli-output/69330060f4f79ec5.html
@@ -0,0 +1,39 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ te A README.new.md
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● f6532f9 just the one
+┊│
+┊├┄qz [feature-bookmarks]
+┊◐223fdd6 feat: Add bookmarks table to store user-tweet rela
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● da395cd Add Spanish README and bookmarks feature
+┊● f55a30e add bookmark model and associations
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/6e0c1402c16f5f6d.html b/public/cache/cli-output/6e0c1402c16f5f6d.html
new file mode 100644
index 0000000..d97867f
--- /dev/null
+++ b/public/cache/cli-output/6e0c1402c16f5f6d.html
@@ -0,0 +1,51 @@
+
+
+
+
+ /usr/local/bin/but status -f
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ te A README.new.md
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● f6532f9 just the one
+┊│ p5 M app/models/user.rb
+┊│ oq A app/views/bookmarks/index.html.erb
+┊│
+┊├┄qz [feature-bookmarks]
+┊◐223fdd6 feat: Add bookmarks table to store user-tweet rela
+┊│ wj A app/models/bookmark.rb
+┊│ i0 M config/routes.rb
+┊│ hq A spacer.txt
+┊│ ww A testing.md
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● da395cd Add Spanish README and bookmarks feature
+┊│ tn M Gemfile
+┊│ uf A README-es.md
+┊│ k1 A app/controllers/bookmarks_controller.rb
+┊│ ia M app/views/dashboard/index.html.erb
+┊● f55a30e add bookmark model and associations
+┊│ ls M app/models/tweet.rb
+┊│ p0 A db/migrate/20250925000001_create_bookmarks.rb
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/6fad05e228ab9095.html b/public/cache/cli-output/6fad05e228ab9095.html
new file mode 100644
index 0000000..dc862c2
--- /dev/null
+++ b/public/cache/cli-output/6fad05e228ab9095.html
@@ -0,0 +1,38 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ ie A app/controllers/bookmarks_controller.rb
+┊ ku M app/models/user.rb
+┊ rv M config/routes.rb
+┊
+┊╭┄nd [user-bookmarks] (no commits)
+┊│ ro M Gemfile
+┊│ pe A app/models/bookmark.rb
+┊│ v0 A app/views/bookmarks/index.html.erb
+├╯
+┊
+┊╭┄h4 [user-changes] (no commits)
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/711027350744cca1.html b/public/cache/cli-output/711027350744cca1.html
new file mode 100644
index 0000000..51207bb
--- /dev/null
+++ b/public/cache/cli-output/711027350744cca1.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
Error: Target '06' not found
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/79a511563972cc8f.html b/public/cache/cli-output/79a511563972cc8f.html
new file mode 100644
index 0000000..653b3ae
--- /dev/null
+++ b/public/cache/cli-output/79a511563972cc8f.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but commit -o -m liked tweets view h4
+
+
+
+
Created commit 90a2ab2 on branch user-changes
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/8350792b8d9d13f6.html b/public/cache/cli-output/8350792b8d9d13f6.html
new file mode 100644
index 0000000..e5067d6
--- /dev/null
+++ b/public/cache/cli-output/8350792b8d9d13f6.html
@@ -0,0 +1,39 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ te A README.new.md
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● 212f9fa Add Spanish README and bookmarks feature
+┊● f6532f9 just the one
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● f55a30e add bookmark model and associations
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/85004f4877113a64.html b/public/cache/cli-output/85004f4877113a64.html
new file mode 100644
index 0000000..abc5ddc
--- /dev/null
+++ b/public/cache/cli-output/85004f4877113a64.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but branch new -a user-bookmarks liked-tweets-stacked
+
+
+
+
Created branch liked-tweets-stacked
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/860ebbd1902dc6d4.html b/public/cache/cli-output/860ebbd1902dc6d4.html
new file mode 100644
index 0000000..05168a9
--- /dev/null
+++ b/public/cache/cli-output/860ebbd1902dc6d4.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but rub 21 f6
+
+
+
+
Squashed 212f9fa → f6532f9
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/8b9c7d07db297441.html b/public/cache/cli-output/8b9c7d07db297441.html
new file mode 100644
index 0000000..ee15d84
--- /dev/null
+++ b/public/cache/cli-output/8b9c7d07db297441.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but rub fd 00
+
+
+
+
Uncommitted fdbd753
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/8df04f3225dfabd3.html b/public/cache/cli-output/8df04f3225dfabd3.html
new file mode 100644
index 0000000..3dd9d30
--- /dev/null
+++ b/public/cache/cli-output/8df04f3225dfabd3.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but rub rt fd
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/8ee36a00318dadf8.html b/public/cache/cli-output/8ee36a00318dadf8.html
new file mode 100644
index 0000000..b14d1df
--- /dev/null
+++ b/public/cache/cli-output/8ee36a00318dadf8.html
@@ -0,0 +1,38 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ wu M README-es.md 🔒 6291859
+┊ te A README.new.md
+┊ rt A app/views/bookmarks/index.html.erb
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● 6291859 Add Spanish README and bookmarks feature
+┊● 0b92478 just the one
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● f3641af add bookmark model and associations {conflicted}
+├╯
+┊
+●32a2175 (common base) [origin/main] Merge pull request #65 from schacon/feature-bookma
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/9184aa27f8f241fd.html b/public/cache/cli-output/9184aa27f8f241fd.html
new file mode 100644
index 0000000..0a1515c
--- /dev/null
+++ b/public/cache/cli-output/9184aa27f8f241fd.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but base update
+
+
+
+
🔄 Updating branches...
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/927c817ed73b68c7.html b/public/cache/cli-output/927c817ed73b68c7.html
new file mode 100644
index 0000000..3ce253e
--- /dev/null
+++ b/public/cache/cli-output/927c817ed73b68c7.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but commit -m all the user bookmarks
+
+
+
+
Created commit e86dada on branch user-bookmarks
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/95763a83f458be55.html b/public/cache/cli-output/95763a83f458be55.html
new file mode 100644
index 0000000..760e4fb
--- /dev/null
+++ b/public/cache/cli-output/95763a83f458be55.html
@@ -0,0 +1,26 @@
+
+
+
+
+ /usr/local/bin/but undo
+
+
+
+
Undoing operation...
+ Reverting to: MoveCommit (2020-10-09 09:06:03)
+✓ Undo completed successfully! Restored to snapshot: 4639f245c06c
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/9d97a568b3786b14.html b/public/cache/cli-output/9d97a568b3786b14.html
new file mode 100644
index 0000000..0b4263a
--- /dev/null
+++ b/public/cache/cli-output/9d97a568b3786b14.html
@@ -0,0 +1,33 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/a52570b3640e3743.html b/public/cache/cli-output/a52570b3640e3743.html
new file mode 100644
index 0000000..31d4fa4
--- /dev/null
+++ b/public/cache/cli-output/a52570b3640e3743.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but rub ku user-changes
+
+
+
+
Assigned app/models/user.rb → [user-changes].
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/a7d718a609ef2081.html b/public/cache/cli-output/a7d718a609ef2081.html
new file mode 100644
index 0000000..4b0260d
--- /dev/null
+++ b/public/cache/cli-output/a7d718a609ef2081.html
@@ -0,0 +1,20 @@
+
'c.
schacon
@
agador.sparticus
,xNMM.
------------------------
.OMMMMo
OS
: macOS 15.7 24G214 arm64
OMMM0,
Host
: Mac15,12
.;loddo:' loolloddol;.
Kernel
: 24.6.0
cKMMMMMMMMMMNWMMMMMMMMMM0:
Uptime
: 44 days, 8 hours, 43 mins
.KMMMMMMMMMMMMMMMMMMMMMMMWd.
Packages
: 203 (brew)
XMMMMMMMMMMMMMMMMMMMMMMMX.
Shell
: zsh 5.9
;MMMMMMMMMMMMMMMMMMMMMMMM:
Resolution
: 1920x1080
:MMMMMMMMMMMMMMMMMMMMMMMM:
DE
: Aqua
.MMMMMMMMMMMMMMMMMMMMMMMMX.
WM
: Quartz Compositor
kMMMMMMMMMMMMMMMMMMMMMMMMWd.
WM Theme
: Blue (Light)
.XMMMMMMMMMMMMMMMMMMMMMMMMMMk
Terminal
: ghostty
.XMMMMMMMMMMMMMMMMMMMMMMMMK.
CPU
: Apple M3
kMMMMMMMMMMMMMMMMMMMMMMd
GPU
: Apple M3
;KMMMMMMMWXXWMMMMMMMk.
Memory
: 4069MiB / 24576MiB
.cooc,. .,coo:.
+
\ No newline at end of file
diff --git a/public/cache/cli-output/b5b56bebb89dedc3.html b/public/cache/cli-output/b5b56bebb89dedc3.html
new file mode 100644
index 0000000..682b518
--- /dev/null
+++ b/public/cache/cli-output/b5b56bebb89dedc3.html
@@ -0,0 +1,35 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ t3 M app/controllers/likes_controller.rb
+┊ ou M app/models/like.rb
+┊
+┊╭┄nd [user-bookmarks]
+┊● e86dada all the user bookmarks
+├╯
+┊
+┊╭┄u1 [liked-tweets] (no commits)
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/bbbd3383f636a9b4.html b/public/cache/cli-output/bbbd3383f636a9b4.html
new file mode 100644
index 0000000..165ca7f
--- /dev/null
+++ b/public/cache/cli-output/bbbd3383f636a9b4.html
@@ -0,0 +1,41 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ wu M README-es.md 🔒 da42d06
+┊ te A README.new.md
+┊ rt A app/views/bookmarks/index.html.erb
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● da42d06 Add Spanish README and bookmarks feature
+┊● fdbd753 just the one
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● f55a30e add bookmark model and associations
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/bd4f40610e40f1b5.html b/public/cache/cli-output/bd4f40610e40f1b5.html
new file mode 100644
index 0000000..76cd5b4
--- /dev/null
+++ b/public/cache/cli-output/bd4f40610e40f1b5.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but commit -m liked tweets changes liked-tweets
+
+
+
+
Created commit 65b17f7 on branch liked-tweets
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/bfafb0a0ab77bc27.html b/public/cache/cli-output/bfafb0a0ab77bc27.html
new file mode 100644
index 0000000..668ce66
--- /dev/null
+++ b/public/cache/cli-output/bfafb0a0ab77bc27.html
@@ -0,0 +1,52 @@
+
+
+
+
+ /usr/local/bin/but status -f
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ wu M README-es.md 🔒 da42d06
+┊ te A README.new.md
+┊ rt A app/views/bookmarks/index.html.erb
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● da42d06 Add Spanish README and bookmarks feature
+┊│ r7 M Gemfile
+┊│ wz A README-es.md
+┊│ y5 A app/controllers/bookmarks_controller.rb
+┊│ k6 M app/views/dashboard/index.html.erb
+┊● fdbd753 just the one
+┊│ v0 M app/models/user.rb
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+┊│ wj A app/models/bookmark.rb
+┊│ i0 M config/routes.rb
+┊│ hq A spacer.txt
+┊│ ww A testing.md
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● f55a30e add bookmark model and associations
+┊│ ls M app/models/tweet.rb
+┊│ p0 A db/migrate/20250925000001_create_bookmarks.rb
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/c197f357f5e62892.html b/public/cache/cli-output/c197f357f5e62892.html
new file mode 100644
index 0000000..8b33c40
--- /dev/null
+++ b/public/cache/cli-output/c197f357f5e62892.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but branch new liked-tweets
+
+
+
+
Created branch liked-tweets
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/d5ab704c72c309c6.html b/public/cache/cli-output/d5ab704c72c309c6.html
new file mode 100644
index 0000000..a4fcc73
--- /dev/null
+++ b/public/cache/cli-output/d5ab704c72c309c6.html
@@ -0,0 +1,39 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ te A README.new.md
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● f6532f9 just the one
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● 221a4a4 Add Spanish README and bookmarks feature
+┊● f55a30e add bookmark model and associations
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/d68eac2e6f9222fb.html b/public/cache/cli-output/d68eac2e6f9222fb.html
new file mode 100644
index 0000000..85cad66
--- /dev/null
+++ b/public/cache/cli-output/d68eac2e6f9222fb.html
@@ -0,0 +1,35 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ nx M Gemfile
+┊ ku M app/models/user.rb
+┊ rv M config/routes.rb
+┊
+┊╭┄kq [liked-tweets-stacked] (no commits)
+┊│
+┊├┄nd [user-bookmarks]
+┊● e677a2e user bookmarks feature
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/d80e33b45a9aaf22.html b/public/cache/cli-output/d80e33b45a9aaf22.html
new file mode 100644
index 0000000..d164d62
--- /dev/null
+++ b/public/cache/cli-output/d80e33b45a9aaf22.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but rub 21 q6
+
+
+
+
Moved 212f9fa → [sc-branch-26]
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/d82bd6f8faa2d86e.html b/public/cache/cli-output/d82bd6f8faa2d86e.html
new file mode 100644
index 0000000..a9b6814
--- /dev/null
+++ b/public/cache/cli-output/d82bd6f8faa2d86e.html
@@ -0,0 +1,38 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ nx M Gemfile
+┊ rv M config/routes.rb
+┊
+┊╭┄nd [user-bookmarks] (no commits)
+┊│ md A app/controllers/bookmarks_controller.rb
+┊│ pe A app/models/bookmark.rb
+┊│ v0 A app/views/bookmarks/index.html.erb
+├╯
+┊
+┊╭┄h4 [user-changes] (no commits)
+┊│ kx M app/models/user.rb
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/de43f2edf09f5070.html b/public/cache/cli-output/de43f2edf09f5070.html
new file mode 100644
index 0000000..644c106
--- /dev/null
+++ b/public/cache/cli-output/de43f2edf09f5070.html
@@ -0,0 +1,41 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ te A README.new.md
+┊ rt A app/views/bookmarks/index.html.erb
+┊
+┊╭┄t5 [gemfile-fixes]
+┊│ q2 M README-es.md 🔒 da42d06
+┊● da42d06 Add Spanish README and bookmarks feature
+┊● fdbd753 just the one
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● f55a30e add bookmark model and associations
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/de923765534564a6.html b/public/cache/cli-output/de923765534564a6.html
new file mode 100644
index 0000000..b809951
--- /dev/null
+++ b/public/cache/cli-output/de923765534564a6.html
@@ -0,0 +1,24 @@
+
+
+
+
+ /usr/local/bin/but commit -m liked tweets changes liked-tweets-stacked
+
+
+
+
Created commit 59db960 on branch liked-tweets-stacked
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/e9ed409178fbcced.html b/public/cache/cli-output/e9ed409178fbcced.html
new file mode 100644
index 0000000..0306b03
--- /dev/null
+++ b/public/cache/cli-output/e9ed409178fbcced.html
@@ -0,0 +1,32 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ nx M Gemfile
+┊ ie A app/controllers/bookmarks_controller.rb
+┊ xw A app/models/bookmark.rb
+┊ ku M app/models/user.rb
+┊ rt A app/views/bookmarks/index.html.erb
+┊ rv M config/routes.rb
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/f2990f43c97b0a13.html b/public/cache/cli-output/f2990f43c97b0a13.html
new file mode 100644
index 0000000..d1a76b6
--- /dev/null
+++ b/public/cache/cli-output/f2990f43c97b0a13.html
@@ -0,0 +1,26 @@
+
+
+
+
+ /usr/local/bin/but rub xw,ie,rt user-bookmarks
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/f2a12b1e97a21933.html b/public/cache/cli-output/f2a12b1e97a21933.html
new file mode 100644
index 0000000..e07ff25
--- /dev/null
+++ b/public/cache/cli-output/f2a12b1e97a21933.html
@@ -0,0 +1,20 @@
+
Overview of the uncommitted changes in the repository
Usage: but status [OPTIONS]
Options:
-f Determines whether the committed files should be shown as well
-v, --verbose Show verbose output with commit author and timestamp
-h, --help Print help
+
\ No newline at end of file
diff --git a/public/cache/cli-output/f3c56e67a1e24971.html b/public/cache/cli-output/f3c56e67a1e24971.html
new file mode 100644
index 0000000..f21319f
--- /dev/null
+++ b/public/cache/cli-output/f3c56e67a1e24971.html
@@ -0,0 +1,42 @@
+
+
+
+
+ /usr/local/bin/but status -f
+
+
+
+
╭┄00 [Unassigned Changes]
+┊
+┊╭┄nd [user-bookmarks]
+┊● e86dada all the user bookmarks
+┊│ ze M Gemfile
+┊│ q4 A app/controllers/bookmarks_controller.rb
+┊│ h8 A app/models/bookmark.rb
+┊│ o9 M app/models/user.rb
+┊│ vc A app/views/bookmarks/index.html.erb
+┊│ rn M config/routes.rb
+├╯
+┊
+┊╭┄u1 [liked-tweets]
+┊● 65b17f7 liked tweets changes
+┊│ pi M app/controllers/likes_controller.rb
+┊│ kx M app/models/like.rb
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/f53ff654c67d268e.html b/public/cache/cli-output/f53ff654c67d268e.html
new file mode 100644
index 0000000..8def83f
--- /dev/null
+++ b/public/cache/cli-output/f53ff654c67d268e.html
@@ -0,0 +1,41 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ wu M README-es.md 🔒 da42d06
+┊ te A README.new.md
+┊ rt A app/views/bookmarks/index.html.erb
+┊
+┊╭┄t5 [gemfile-fixes]
+┊● da42d06 Add Spanish README and bookmarks feature
+┊● fdbd753 just the one
+┊│
+┊├┄qz [feature-bookmarks]
+┊●223fdd6 feat: Add bookmarks table to store user-tweet rela
+├╯
+┊
+┊╭┄q6 [sc-branch-26]
+┊● 58d78ca add bookmark model and associations
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/f7e1c84e128c0e56.html b/public/cache/cli-output/f7e1c84e128c0e56.html
new file mode 100644
index 0000000..d68c943
--- /dev/null
+++ b/public/cache/cli-output/f7e1c84e128c0e56.html
@@ -0,0 +1,35 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
╭┄00 [Unassigned Changes]
+┊ nx M Gemfile
+┊ ie A app/controllers/bookmarks_controller.rb
+┊ xw A app/models/bookmark.rb
+┊ ku M app/models/user.rb
+┊ rt A app/views/bookmarks/index.html.erb
+┊ rv M config/routes.rb
+┊
+┊╭┄nd [user-bookmarks] (no commits)
+├╯
+┊
+●204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
+
+
+
\ No newline at end of file
diff --git a/public/cache/cli-output/f93e374ac2785a90.html b/public/cache/cli-output/f93e374ac2785a90.html
new file mode 100644
index 0000000..8a4b1de
--- /dev/null
+++ b/public/cache/cli-output/f93e374ac2785a90.html
@@ -0,0 +1,34 @@
+
+
+
+
+ /usr/local/bin/but status
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/cli-examples/but-st-c43eb45a.html b/public/cli-examples/but-st-c43eb45a.html
new file mode 100644
index 0000000..b90359a
--- /dev/null
+++ b/public/cli-examples/but-st-c43eb45a.html
@@ -0,0 +1,33 @@
+
+
+
+
+ but st
+
+
+
+
╭┄00 [Unassigned Changes]
+┊
+┊╭┄jw [sc-better-tempfiles]
+┊●fc3d68e bump version
+┊●0751159 Use system temp directory instead of hardcoded /tm
+┊●9c7e2c5 Include full command in tempfile name
+┊●60c165c Hash output when generating filenames
+├╯
+┊
+●d9719d1 (common base) [origin/main] Merge pull request #2 from schacon/sc-bump-version
+
+
+
\ No newline at end of file
diff --git a/public/llms-full.txt b/public/llms-full.txt
index 5d94e45..140abb6 100644
--- a/public/llms-full.txt
+++ b/public/llms-full.txt
@@ -13,6 +13,7 @@
- [fetch-push](#fetch-push.mdx)
- [recovering-stuff](#recovering-stuff.mdx)
- [agents-tab](#agents-tab.mdx)
+- [gerrit-mode](#gerrit-mode.mdx)
- [timeline](#timeline.mdx)
- [github-integration](#github-integration.mdx)
- [gitlab-integration](#gitlab-integration.mdx)
@@ -28,48 +29,39 @@
- [virtual-branches](#virtual-branches.mdx)
- [ai-overview](#ai-overview.mdx)
- [claude-code-hooks](#claude-code-hooks.mdx)
+- [cursor-hooks](#cursor-hooks.mdx)
- [mcp-server](#mcp-server.mdx)
- [debugging](#debugging.mdx)
- [contact-us](#contact-us.mdx)
- [open-source](#open-source.mdx)
- [supporters](#supporters.mdx)
-
-
-# index.mdx
-
-## Overview
-
-GitButler is a new Source Code Management system designed to manage your branches, record and backup your work, be your Git client, help with your code and much more. Our focus is everything after writing code in your editor and before sharing it on GitHub. We're focused on your working directory and how we can help with everything you do there.
-
-
-
-Here you will find documentation on the product and the way that we're running our beta and building our product with your help.
-
-## Getting Started
-
-Check out our [Getting Started](/guide) guide to get started with GitButler, or check out our helpful video overview:
-
-https://www.youtube.com/watch?v=DhJtNNhCNLM
-
-## Why We're Doing This
-
-Read about it over [Why GitButler](/why-gitbutler) Section.
-
-## What We Do
-
-The GitButler client is a powerful Git client. You can manage your branches, work on multiple things at once, push and fetch from your Git server, easily rebase and modify commits and more. We have a unique approach to merge conflicts that help split up any conflicting work. It also keeps a timeline so that you can easily undo any operation.
-
-- [Virtual Branches](/features/branch-management/virtual-branches)
-- [First Class Conflicts](/features/branch-management/merging)
-- [Project History](/features/timeline)
-
-
+- [but-base](#but-base.mdx)
+- [but-branch](#but-branch.mdx)
+- [but-commit](#but-commit.mdx)
+- [but-describe](#but-describe.mdx)
+- [but-forge](#but-forge.mdx)
+- [but-init](#but-init.mdx)
+- [but-mark](#but-mark.mdx)
+- [but-mcp](#but-mcp.mdx)
+- [but-new](#but-new.mdx)
+- [but-oplog](#but-oplog.mdx)
+- [but-publish](#but-publish.mdx)
+- [but-push](#but-push.mdx)
+- [but-restore](#but-restore.mdx)
+- [but-rub](#but-rub.mdx)
+- [but-snapshot](#but-snapshot.mdx)
+- [but-status](#but-status.mdx)
+- [but-undo](#but-undo.mdx)
+- [but-unmark](#but-unmark.mdx)
+- [installation](#installation.mdx)
+- [branching-and-commiting](#branching-and-commiting.mdx)
+- [editing-commits](#editing-commits.mdx)
+- [initializing-a-repository](#initializing-a-repository.mdx)
+- [operations-log](#operations-log.mdx)
+- [rubbing](#rubbing.mdx)
+- [scripting](#scripting.mdx)
+- [tutorial-overview](#tutorial-overview.mdx)
+- [updating-the-base](#updating-the-base.mdx)
# butler-flow.mdx
@@ -340,142 +332,6 @@ Just click the "Operations History" button in the sidebar, find the action you w
/>
-# releases.mdx
-
-
-GitButler is released on a regular basis in two separate tracks. Their version numbers are incremented independently.
-
-1. **Release** - stable releases
-2. **Nightly** - development releases built at minimum 1x per day via GitHub Actions.
-
-
-
-You can find the download links and changelogs for the latest releases on our [GitHub Releases](https://github.com/gitbutlerapp/gitbutler/releases).
-
-
-## Platforms
-
-We bundle and ship GitButler for Mac OS, Windows, and Linux.
-
-### Windows
-
-| Arch | Format | In-app updater |
-| --- | --- | --- |
-| `x86_64` | `msi` | |
-
-### Mac OS
-
-| Arch | Format | In-app updater |
-| --- | --- | --- |
-| `x86_64` | `dmg` | |
-| `arm64` | `dmg` | |
-
-### Linux
-
-| Arch | Format | In-app updater |
-| --- | --- | --- |
-| `x86_64` | `deb` | |
-| `x86_64` | `rpm` | |
-| `x86_64` | `AppImage` | |
-
-> Support for the Linux releases are complicated a bit through a core dependency of our framework, `libwebkit2gtk`, which is used to provide the web view on Linux. Tauri v1 required `libwebkit2gtk-4.0` which is not available in Ubuntu 24.04 or Debian 13 and newer.
->
-> We've recently upgraded to Tauri v2 (as of Nightly `0.5.845` and Release `0.13.9`), and it now requires `libwebkit2gtk-4.1`. This version of the package is not available in the repositories for Ubuntu 20.04 and older as well as Debian 11 and older.
->
-> For more information, check out the [pinned issue](https://github.com/tauri-apps/tauri/issues/9662) in the Tauri repository.
-
-
-# why-gitbutler.mdx
-
-The GitButler manifesto, as it were.
-
-Everyone loves a good manifesto. So, why is there a need for a new Git client in the world? Don't we have enough? Isn't the command line just fine?
-
-Having cofounded GitHub, trained dozens of corporate teams on distributed version control tools and literally written the book on Git, we have spent a lot of time and energy over the last decade thinking about the source code management tools that software developers use every day.
-
-GitHub has changed the way that millions of developers across the world collaborate and work with their source code, but as sophisticated and user friendly as that tool is in our daily coding lives, using GitHub or GitLab or Bitbucket still requires all of those developers to work with a command line tool that is confusing, difficult to use, error prone and not originally designed or built for the workflows and processes that most developers today use. That tool is Git.
-
-Sure, some small minority will use a GUI of some sort, but even those tools are mostly wrappers around the core concepts of Git itself, never reimagining what source code management could be or if Git itself is actually good at helping them with the tasks they face on a daily basis. I've never personally used one because they do little that Git itself doesn't and honestly it's generally easier to just do those tasks on the command line, where it's quick and efficient and I don't have to take my hands off the keyboard.
-
-But what if we broke down everything that you try to accomplish with Git, with source code management tools in general, reduce them down to first principles and imagine a tool that does all of those things better? Are you using Git because it's the best way you can imagine accomplishing those tasks, or are you using it because it's what is there, it's what works with GitHub, it's the only real option?
-
-The reality is that source code management tools have changed very little on a fundamental level in the last 40 years. If you look at the tools and commands and interface that RCS had in the 80s, or Subversion had in the 90s, is it really massively different than how you use Git today on a daily basis?
-
-Yes, Git has easy branching, acceptable merging, a nice network transport method to move your code around, but you're still making manual checkins, you're still trying to remember obscure arguments, you're still losing work when things get complicated.
-
-GitButler is rethinking everything between when you write code in your editor of choice and when you push that code to GitHub for review. Why are you making 'wip' commits when your SCM should be recording everything for you? Why are everyone's commit messages close to useless? Why is `git blame` the best way to get context on the code your team has written? Why can't you seamlessly transition work between computers?
-
-We are creating not only a new kind of Git client, but an entirely new way of thinking about managing the code that you work on. A tool that helps you at every step of the software development process. A code concierge, hard at work for you to ensure that you'll never lose a moment of work again. That you'll have all the context and support you'll need around every line of code you work on.
-
-Managing your source code can be different, smarter, leaps ahead of the 40 year old concepts that we're using today.
-
-Our goal is to make sure that nobody ever has to read Scott's book again. That you don't have to learn how to manage your source code management tool.
-
-
-
-
-# custom-csp.mdx
-
-
-By default GitButler uses a strict Content Security Policy (CSP) to protect against various attacks, such as cross-site scripting (XSS) and data injection attacks. This policy restricts the sources from which content can be loaded, as well as the hosts the application can connect to, ensuring that only trusted sources are allowed.
-
-However, there are some cases where you may need to customize the CSP to allow certain features or integrations. Some examples include:
-- Self-hosted GitHub Enterprise instances
-- Self-hosted GitLab instances
-- Self-hosted Ollama instances
-
-In those cases you are likely to observe an error message that looks something like this:
-
-```
-Refused to connect to https://./api/v4/projects/9/merge_requests because it does not appear in the connect-src directive of the Content Security Policy.
-```
-
-You can resolve this issue by adding your host to the CSP.
-
-## Adding a Custom CSP
-
-You can add a custom CSP by editing the GitButler configuration file, found at the following location:
-
-
-
- ```bash
- ~/Library/Application\ Support/gitbutler/settings.json
- ```
-
-
- ```bash
- C:\Users\[username]\AppData\Roaming\gitbutler\settings.json
- ```
-
-
- ```bash
- ~/.config/gitbutler/settings.json
- ```
-
-
-
-The file is in JSONC format and follows the [following schema](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but-settings/assets/defaults.jsonc)
-
-In order to add your custom CSP entry, you want to add an `extraCsp` entry to the JSON file. The `extraCsp` entry is an object that contains a `hosts` array, which is where you can add your custom hosts. For example:
-
-```json
-"extraCsp": {
- "hosts": ["https://subdomain.example.com", "http://another-subdomain.example.com"]
-}
-```
-
-Note that if `extraCsp` is the only entry in the JSON file, you may want to enclose it in a top-level object, like this:
-
-```json
-{
- "extraCsp": {
- "hosts": ["https://subdomain.example.com", "http://another-subdomain.example.com"]
- }
-}
-```
-
-The changes will take effect the next time you start GitButler.
-
# overview.mdx
@@ -618,135 +474,78 @@ Once the branch has been merged, it will automatically mark the review as closed
The author can also manually close the review if they decide to abandon the series.
-# ai-assistance.mdx
+# why-gitbutler.mdx
-## Getting Started
+The GitButler manifesto, as it were.
-### Global AI Setup (One-time)
+Everyone loves a good manifesto. So, why is there a need for a new Git client in the world? Don't we have enough? Isn't the command line just fine?
-1. Navigate to **Global settings** → **AI Options**
-2. Choose your AI provider:
- - **GitButler API** (default): Uses OpenAI through GitButler's servers - no API key needed
- - **Your own key**: Bring your own OpenAI, Claude, Ollama, or LM Studio credentials
-3. If using your own key, enter your API credentials
+Having cofounded GitHub, trained dozens of corporate teams on distributed version control tools and literally written the book on Git, we have spent a lot of time and energy over the last decade thinking about the source code management tools that software developers use every day.
-### Per-Project Setup
+GitHub has changed the way that millions of developers across the world collaborate and work with their source code, but as sophisticated and user friendly as that tool is in our daily coding lives, using GitHub or GitLab or Bitbucket still requires all of those developers to work with a command line tool that is confusing, difficult to use, error prone and not originally designed or built for the workflows and processes that most developers today use. That tool is Git.
-1. Open **Project settings** → **AI options**
-2. Enable **"Enable branch and commit message generation"**
-3. Optionally enable **"Enable experimental AI features"** for advanced functionality
+Sure, some small minority will use a GUI of some sort, but even those tools are mostly wrappers around the core concepts of Git itself, never reimagining what source code management could be or if Git itself is actually good at helping them with the tasks they face on a daily basis. I've never personally used one because they do little that Git itself doesn't and honestly it's generally easier to just do those tasks on the command line, where it's quick and efficient and I don't have to take my hands off the keyboard.
-## Features
+But what if we broke down everything that you try to accomplish with Git, with source code management tools in general, reduce them down to first principles and imagine a tool that does all of those things better? Are you using Git because it's the best way you can imagine accomplishing those tasks, or are you using it because it's what is there, it's what works with GitHub, it's the only real option?
-### Branch Name Generation
+The reality is that source code management tools have changed very little on a fundamental level in the last 40 years. If you look at the tools and commands and interface that RCS had in the 80s, or Subversion had in the 90s, is it really massively different than how you use Git today on a daily basis?
-Automatically creates descriptive, kebab-case branch names based on your code changes.
+Yes, Git has easy branching, acceptable merging, a nice network transport method to move your code around, but you're still making manual checkins, you're still trying to remember obscure arguments, you're still losing work when things get complicated.
-**Usage:**
+GitButler is rethinking everything between when you write code in your editor of choice and when you push that code to GitHub for review. Why are you making 'wip' commits when your SCM should be recording everything for you? Why are everyone's commit messages close to useless? Why is `git blame` the best way to get context on the code your team has written? Why can't you seamlessly transition work between computers?
-- Right-click on a branch header and select **Generate branch name**
-- GitButler analyzes commit messages in the branch and suggests an appropriate name
-- Generated names use kebab-case format and avoid conflicts with existing branches
+We are creating not only a new kind of Git client, but an entirely new way of thinking about managing the code that you work on. A tool that helps you at every step of the software development process. A code concierge, hard at work for you to ensure that you'll never lose a moment of work again. That you'll have all the context and support you'll need around every line of code you work on.
-### Commit Message Generation
+Managing your source code can be different, smarter, leaps ahead of the 40 year old concepts that we're using today.
-Creates professional commit messages following best practices.
+Our goal is to make sure that nobody ever has to read Scott's book again. That you don't have to learn how to manage your source code management tool.
-**Features:**
+
-- Semantic prefixes (`feat:`, `fix:`, `refactor:`)
-- 50-character title limit, 72-character body wrap
-- Explains what changed and why
-- Real-time streaming as AI generates the message
-- Based on actual code diffs, not just file names
-**Usage:**
+# releases.mdx
-1. Make changes to your files (staging is automatic in GitButler)
-2. Click the **Generate message** button in the commit message editor
-3. AI streams the generated message in real-time
-4. Review and edit before committing
-**Example format:**
+GitButler is released on a regular basis in two separate tracks. Their version numbers are incremented independently.
-```
-feat: add user authentication system
+1. **Release** - stable releases
+2. **Nightly** - development releases built at minimum 1x per day via GitHub Actions.
-Implements JWT-based authentication with login and registration
-endpoints. Includes password hashing and session management
-to secure user accounts.
-```
-### Pull Request Descriptions
+
+You can find the download links and changelogs for the latest releases on our [GitHub Releases](https://github.com/gitbutlerapp/gitbutler/releases).
+
-Generates comprehensive PR descriptions when creating pull requests.
+## Platforms
-**Usage:**
+We bundle and ship GitButler for Mac OS, Windows, and Linux.
-1. Create a pull request from your branch
-2. In the PR creation dialog, click **Generate PR description**
-3. AI analyzes all commits in your branch and generates a description
-4. Review and edit the generated content before creating the PR
+### Windows
-**Generated content includes:**
+| Arch | Format | In-app updater |
+| --- | --- | --- |
+| `x86_64` | `msi` | |
-- High-level summary based on commit messages
-- Structured description following PR templates (if configured)
-- Context derived from the changes in your branch
+### Mac OS
-## Advanced Features
+| Arch | Format | In-app updater |
+| --- | --- | --- |
+| `x86_64` | `dmg` | |
+| `arm64` | `dmg` | |
-### Custom Prompts
-
-- **Global Prompts**: Create custom prompts in **Global settings** → **AI Options**
-- **Project-Specific Prompts**: Assign specific prompts per project in **Project settings** → **AI options**
-- **Commit & Branch Prompts**: Separate customization for commit messages and branch names
-
-## Configuration
-
-### Global Settings (Global settings → AI Options)
-
-- **AI Provider**: Choose between OpenAI, Anthropic, Ollama, or LM Studio
-- **Key Options**: Use GitButler API or bring your own credentials for each provider
-- **Model Selection**: Choose specific models per provider (GPT-4o, Claude Sonnet, etc.)
-- **Amount of provided context**: Set how many characters of git diff to send to AI
-
-### Project Settings (Project settings → AI options)
-
-- **Enable branch and commit message generation**: Master toggle for AI features in this project
-- **Enable experimental AI features**: Access to advanced AI functionality (requires GitButler API)
-- **Custom prompts**: Assign specific prompts from global settings to this project for commits and branches
-
-## Troubleshooting
-
-**AI features not working?**
-
-1. **Check Global Settings**: Navigate to **Global settings** → **AI Options** and verify:
- - AI provider is configured (OpenAI, Anthropic, etc.)
- - Key option is selected (GitButler API or your own key)
- - If using your own key, ensure it's entered correctly
- - Model is selected for your chosen provider
-2. **Check Project Settings**: Open **Project settings** → **AI options** and ensure:
- - **"Enable branch and commit message generation"** is turned ON
- - This setting must be enabled for each project individually
-3. **Verify API Access**: Ensure sufficient API quota and valid credentials
-
-**AI buttons not appearing?**
-
-- The project-level toggle in **Project settings** → **AI options** controls button visibility
-- Without this enabled, Generate buttons won't appear in the UI
-
-**Need better suggestions?**
-
-- Customize prompt templates in **Global settings** → **AI Options**
-- Make meaningful code changes with clear patterns
-- Use descriptive variable names and comments in your code
-- Review [troubleshooting guide](https://docs.gitbutler.com/troubleshooting/custom-csp) for advanced configurations
+### Linux
-## Related Documentation
+| Arch | Format | In-app updater |
+| --- | --- | --- |
+| `x86_64` | `deb` | |
+| `x86_64` | `rpm` | |
+| `x86_64` | `AppImage` | |
-- [AI Integration Overview](../ai-integration/ai-overview)
-- [MCP Server Setup](../ai-integration/mcp-server)
+> Support for the Linux releases are complicated a bit through a core dependency of our framework, `libwebkit2gtk`, which is used to provide the web view on Linux. Tauri v1 required `libwebkit2gtk-4.0` which is not available in Ubuntu 24.04 or Debian 13 and newer.
+>
+> We've recently upgraded to Tauri v2 (as of Nightly `0.5.845` and Release `0.13.9`), and it now requires `libwebkit2gtk-4.1`. This version of the package is not available in the repositories for Ubuntu 20.04 and older as well as Debian 11 and older.
+>
+> For more information, check out the [pinned issue](https://github.com/tauri-apps/tauri/issues/9662) in the Tauri repository.
# fetch-push.mdx
@@ -840,149 +639,67 @@ If none of the available options helps, feel free to hop on our [Discord](https:
-# timeline.mdx
-
-
-Undo nearly any of your actions or go back in time to an earlier state.
-
-## How it works
-
-Before GitButler does any major action, it records the state of everything (your virtual branch state, your uncommitted work, conflict state, etc) and stores it in your Git object database as snapshots. You can hit the 'revert' button on any of the entries and it will restore the state of all of these things to what they looked like when they were recorded, letting you go back in time.
-
-
-
-## Restoring State
-
-If you hover over any of the entries, you will see a button named "Revert" that will restore the state of things to right before you did that action. So if you revert one that says "Create Commit", it will put you where you were right before you made that commit.
-
-
-
-## Recovering Content
-
-Occasionally, GitButler will also take snapshots of files that were changed recently, even if they weren't committed. If this, or any other action, sees changes in files, you can see which ones and view the change by clicking on the file name.
-
-
-
-
-# github-integration.mdx
-
-
-GitButler integrates seamlessly with GitHub, allowing you to manage the pull requests associated with your branches directly from the GitButler client.
-
-## Setting up GitHub Integration
-
-To set up GitHub integration, you need to connect your GitButler client to your GitHub account. This is done through the GitButler settings.
-
-Open the GitButler settings by clicking on the gear icon in the sidebar, then navigate to the "Integrations" tab, then click on the "Authorize" button for GitHub.
-
-
-
-Now copy the generated code and open the GitHub authorization page in your browser.
-
-
-
-Once you are on the GitHub authorization page, paste the code into the input field and it should let you know that you are authorized.
-
-
-
-## Using GitButler to Manage Pull Requests
-
-Once you have set up GitHub integration, you can open your pull requests directly from the GitButler branch.
-
-
-
-When you create a new branch or commit changes, GitButler will automatically check if there are any associated pull requests on GitHub. You can view these pull requests in the "Pull Requests" tab in the sidebar of the Branches page.
-
-
-# gitlab-integration.mdx
-
-
-Create your GitLab Merge requests without leaving GitButler.
-
-## Setting up the GitLab Integration
-
-In order to set up the GitLab integration, you will need two things. Firstly, a GitLab Personal Token and the Project ID for your project.
-
-### Creating a GitLab Personal Token
-
-To create a GitLab Personal Token, navigate to your GitLab preferences, and then click on the "Access tokens" tab. You will then see an "Add new token" button. Pressing this button will provide you with a form (as captured below) where you can specify the token name, expiration date, and scopes. We currently require the `api` scope. In this example, I've chosen to set the expiration date for the token to a year from now.
-
-
-
-On completion, you will be showen the following screen which contains your GitLab Personal token.
-
-
+# custom-csp.mdx
-### Finding your GitLab Project ID
-Navigate to the main page of the repository you want to configure the integration for. On the top left next to the "Fork" button, you will see three dots. Click on those three dots, and click on "Copy project ID". This will copy the GitLab Project ID to your clipboard.
+By default GitButler uses a strict Content Security Policy (CSP) to protect against various attacks, such as cross-site scripting (XSS) and data injection attacks. This policy restricts the sources from which content can be loaded, as well as the hosts the application can connect to, ensuring that only trusted sources are allowed.
-
+However, there are some cases where you may need to customize the CSP to allow certain features or integrations. Some examples include:
+- Self-hosted GitHub Enterprise instances
+- Self-hosted GitLab instances
+- Self-hosted Ollama instances
-### Configuring GitButler
+In those cases you are likely to observe an error message that looks something like this:
-Inside GitButler, navigate to the project settings by clicking on the small cog icon in the bottom left. Scroll down to the bottom of the "Project" tab where you will see a form for entering your GitLab details. The provided GitLab Personal Token will be stored securly in your operating system's keychain.
+```
+Refused to connect to https://./api/v4/projects/9/merge_requests because it does not appear in the connect-src directive of the Content Security Policy.
+```
-
+You can resolve this issue by adding your host to the CSP.
-### Custom GitLab Instances
+## Adding a Custom CSP
-You may also provide a different Instance URL if you are using a self-hosted GitLab instance.
+You can add a custom CSP by editing the GitButler configuration file, found at the following location:
-Note that if you use a custom GitLab instance, you will likely need to configure a custom CSP (Content Security Policy) to allow GitButler to connect to it. You can find more information on how to do that in the [Custom Content Security Policy (CSP)](/troubleshooting/custom-csp) section of the documentation.
+
+
+ ```bash
+ ~/Library/Application\ Support/gitbutler/settings.json
+ ```
+
+
+ ```bash
+ C:\Users\[username]\AppData\Roaming\gitbutler\settings.json
+ ```
+
+
+ ```bash
+ ~/.config/gitbutler/settings.json
+ ```
+
+
-## Usage
+The file is in JSONC format and follows the [following schema](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but-settings/assets/defaults.jsonc)
-You will now have a "Submit for Review" button on each branch which you can use to create a Merge Request.
+In order to add your custom CSP entry, you want to add an `extraCsp` entry to the JSON file. The `extraCsp` entry is an object that contains a `hosts` array, which is where you can add your custom hosts. For example:
-
+```json
+"extraCsp": {
+ "hosts": ["https://subdomain.example.com", "http://another-subdomain.example.com"]
+}
+```
-Once the Merge Request is created you will be able to see the status of it from within the client. Please note that we do not yet integrate with CI status.
+Note that if `extraCsp` is the only entry in the JSON file, you may want to enclose it in a top-level object, like this:
-
+```json
+{
+ "extraCsp": {
+ "hosts": ["https://subdomain.example.com", "http://another-subdomain.example.com"]
+ }
+}
+```
+The changes will take effect the next time you start GitButler.
# recovering-stuff.mdx
@@ -1458,1166 +1175,3321 @@ As files are assigned and committed to your branch, you can see an overview of t
/>
-# merging.mdx
-
+# gerrit-mode.mdx
-By default, GitButler rebases the work on your virtual branches when you update your target branch (upstream) work.
+Not _everyone_ uses GitHub or GitLab to review code and collaborate. If you use the [Gerrit](https://www.gerritcodereview.com/) code review tool, GitButler has a mode for you! In fact, GitButler is the best Gerrit client there is.
-Often this works just fine and the commits are simply rebased. Occasionally, you will have conflicts with upstream work.
+## What is Gerrit
-In this case, GitButler will not do what Git normally does, which is to stop at each conflicted commit and make you fix it before moving on. Instead, it will apply the changes that it can and store the commit as a "conflicted" commit and continue the rebasing process.
+If you've never heard of Gerrit, it's used by large teams like the Android or Chrome projects to manage huge numbers of changes and users across large numbers of interdependent repositories.
-When you go to update from upstream, GitButler will show you all the branches that it will rebase and will let you know if any of them will have conflicts:
+Here is an example of incoming changesets on the [Android project](https://android-review.googlesource.com/q/status:open+-is:wip,50):
-In this case, when you perform the rebase, that branch will then contain "conflicted" commits. They will be marked in the UI as conflicted and you can click on them to get a "resolve conflict" button to start the resolution process.
+## How is Gerrit different than Pull/Merge Requests?
+
+Good question. With GitHub or GitLab, when you send a pull/merge request, the review process is branch based. If you add more commits on top of your branch, the changes are squished into one big unified diff for review. Most teams tend to avoid rebasing anything that was already shared.
+
+Gerrit is a commit based review system. Every review is based on exactly one commit. It's very common to edit shared commits and submit new versions of them to address feedback.
+
+This model works _very well_ with GitButler's easy [commit editing](/features/branch-management/commits) features. With any other Git client, interactive rebasing and amending tends to be quite painful and error prone, making it fairly difficult to work with Gerrit's model. With GitButler, it's ideal. Just drag and drop changes and update your changesets easily.
+
+## How to turn on Gerrit Mode
+
+To turn on Gerrit Mode in GitButler, you just have to set a Git config option called `gitbutler.gerritMode` in the project you want to act in a Gerrit compatible fashion:
+
+```
+❯ cd my_project
+❯ git config gitbutler.gerritMode 1
+```
+
+## What is Gerrit Mode
+
+Now GitButler will change it's behavior in the following ways:
+
+- When you commit, we will automatically inject a `Change-Id` trailer into the commit in the format that Gerrit expects. You do not need to [setup a `commit-msg` hook](https://gerrit-review.googlesource.com/Documentation/cmd-hook-commit-msg.html) like you do with other Git clients.
+- When you push, it will not push to a matching branch name on the remote. Instead it will push to `refs/for/main` (or whatever the name of the target branch is set to be).
+- After a push, we record the change url and show you the link and number for each commit automatically.
-When you click that, GitButler will remove the other virtual branches and other work from your working directory and check out just this commit with it's conflict markers. It will show you a special "edit mode" screen, where you are directly editing this commit.
+We can also set some extra push options when we push, including:
+
+- [Topics](https://gerrit-review.googlesource.com/Documentation/cross-repository-changes.html)
+- [Hashtags](https://gerrit-review.googlesource.com/Documentation/intro-user.html#hashtags)
+- [WIP status](https://gerrit-review.googlesource.com/Documentation/intro-user.html#wip)
-If you want to cancel this conflict resolution, you can just hit 'Cancel' and it will go back to your normal state. If you have fixed all the issues, you can click "Save and Exit" and it will commit the conflict resolution and if needed, rebase any further commits on that branch on top of your new work.
-
-
-
-
-# pushing-and-fetching.mdx
-
-
-GitButler can authenticate with an upstream Git server in several different ways.
-
- You can just tell us to use the system Git executable, which you can setup however you want. You can use our built in SSH protocol with your own SSH key (this does not require you to have Git installed), or you can use the default [Git credentials helper](https://git-scm.com/doc/credential-helpers).
-
-You can set your preference (and test if it works) in your project's "Git authentication" section:
-
-
-Once that's done, GitButler will be able to automatically fetch upstream work and push new branches to your upstream server.
-
-
-# branch-lanes.mdx
+# timeline.mdx
-All of your branches - remote, local, and virtual / applied or not - are managed in the Branch Tab. This is where you can see all of your branches, apply them to your workspace, and manage your virtual branches.
+Undo nearly any of your actions or go back in time to an earlier state.
-You can access the Branches tab by clicking on the "Branches" icon in the sidebar.
+## How it works
-The interface looks something like this:
+Before GitButler does any major action, it records the state of everything (your virtual branch state, your uncommitted work, conflict state, etc) and stores it in your Git object database as snapshots. You can hit the 'revert' button on any of the entries and it will restore the state of all of these things to what they looked like when they were recorded, letting you go back in time.
-## Branch List
-
-The first pane on the left shows you the virtual branches and stacks that you have as well as the other branches that you have available (legacy git branches, remote branches and PRs).
-
-All of these branches can be converted into virtual branches by clicking them and then clicking the "Apply to workspace" button on the top of the branch view (middle pane).
-
-Local branches can also be fully deleted here.
-
-### Current Workspace Target
+## Restoring State
-The "Current workspace target" is the view of the target branch that you've set. It will show you essentially a `git log` of `origin/master` or whatever you set as your target branch, and it will show you if there are any commits upstream that you have not integrated locally yet. We will automatically check for new upstream changes every few minutes, but you can also click the update button to check immediately.
+If you hover over any of the entries, you will see a button named "Revert" that will restore the state of things to right before you did that action. So if you revert one that says "Create Commit", it will put you where you were right before you made that commit.
+## Recovering Content
-# commits.mdx
-
-
-GitButler gives you a lot of tooling for creating, modifying, squashing, splitting and undoing commits.
-
-GitButler has lots of ways to craft the exact commits that you want to end up with. With other Git clients, you tend to have to run somewhat complicated `git rebase -i` type commands to change commit messages, split a commit up or absorb new changes into an existing commit. With GitButler, most of these are simply drag-and-drop operations.
-
-Here are some of the cool things you can do very easily with GitButler.
-
-## Creating Commits
-
-Once you have changes on a virtual branch and want to commit them, you can hit the "Start a Commit" button in any lane, which gives you an editor to write a summary and optional description for your commit message.
+Occasionally, GitButler will also take snapshots of files that were changed recently, even if they weren't committed. If this, or any other action, sees changes in files, you can see which ones and view the change by clicking on the file name.
-If you want AI to use your diff to generate a commit message, you can hit the "Generate message" button.
-
-## AI Commit Message Settings
-
-If you want to use AI for generating your commit messages or branch names from time to time, there are quite a few options in your user preferences. You can choose from [OpenAI](https://platform.openai.com/), [Anthropic](https://www.anthropic.com/), [Ollama](https://www.ollama.com/) or [LM Studio](https://lmstudio.ai/) as your engine.
-
-For both OpenAI and Anthropic, you can either use your own API key to directly send your request to their servers, or you can proxy via our server (which you need to be logged in for).
-
-
-
-If you use your own key for OpenAI or Anthropic, you can choose which model you would like us to use.
-
-
-
-If you don't want to send your diff to another server, you can also use Ollama or LM Studio, which are a local LLM servers.
-
-With Ollama, you can run nearly any open source large language model ([Llama 3](https://www.ollama.com/library/llama3), [Phi 3](https://www.ollama.com/library/phi3), [Mistral](https://www.ollama.com/library/mistral), [Gemma](https://www.ollama.com/library/gemma), etc) entirely locally.
-
-Note that if you choose to configure a self-hosted Ollama server, you will likely need to add a custom CSP (Content Security Policy) to allow GitButler to connect to it.
-
-You can find more information on how to do that in the [Custom Content Security Policy (CSP)](/troubleshooting/custom-csp) section of the documentation.
-
-With all of these models, you can also customize the prompt if you want something more specific. In the "Custom AI prompts" section, you can add new prompts and select which one you want to use per project. This is useful for following certain formats or generating messages in other languages, etc.
-
+# github-integration.mdx
-Custom prompts can contain three variables which we will replace with the appropriate values. Those include:
-- `%{emoji_style}` - Instructs the LLM whether or not to make use of [GitMoji](https://gitmoji.dev) in the title prefix, based on your settings.
-- `%{brief_style}` - Instructs the LLM to not exceed 1 sentence when generating the commit message.
-- `%{diff}` - The contents of the diff.
-- `%{branch_name}` - The name of the current branch. Available in "Commit Message" custom prompt only.
+GitButler integrates seamlessly with GitHub, allowing you to manage the pull requests associated with your branches directly from the GitButler client.
-## Absorbing New Work
+## Setting up GitHub Integration
-If you have a commit and get some feedback on it or find an issue and wish to amend it, you can very easily absorb changes into existing commits. Simply drag the file into the commit you want to absorb that change into and drop it there.
+To set up GitHub integration, you need to connect your GitButler client to your GitHub account. This is done through the GitButler settings.
-This will both rewrite that commit to include the new changes and also rebase every commit upstream from it automatically.
+Open the GitButler settings by clicking on the gear icon in the sidebar, then navigate to the "Integrations" tab, then click on the "Authorize" button for GitHub.
-## Undoing Commits
-
-You can easily undo any commit in your stack by expanding the commit and hitting the 'Undo' button. This will rebase all the commits above it and leave whatever work was in that commit as new uncommitted changes.
-
-
- "Undo"ing a commit does not throw it away, it simply makes that work not in a commit anymore. It
- will not discard the changes.
-
+Now copy the generated code and open the GitHub authorization page in your browser.
-## Undoing One File in a Commit
-
-If you want to undo a single file in a commit, you can expand the commit and click on the file you want to undo. Then hit "Uncommit". This will remove that file from the commit and leave it as uncommitted changes.
+Once you are on the GitHub authorization page, paste the code into the input field and it should let you know that you are authorized.
-## Squashing Commits
+## Using GitButler to Manage Pull Requests
-Squashing two commits into a single combined commit is also very simple. Just drag one commit on top of another one.
+Once you have set up GitHub integration, you can open your pull requests directly from the GitButler branch.
-## Splitting Commits
+When you create a new branch or commit changes, GitButler will automatically check if there are any associated pull requests on GitHub. You can view these pull requests in the "Pull Requests" tab in the sidebar of the Branches page.
-Splitting commits is slightly more complex. GitButler allows you to create an "empty" commit anywhere and then drag changes into it. Here is an example of creating an empty commit between two other commits, dragging changes from both of them into it and then absorbing new work into it as well.
-
+# gitlab-integration.mdx
-You can also notice that I easily edit the commit message by just hitting the "edit message" button.
-## Moving Commits
+Create your GitLab Merge requests without leaving GitButler.
-You can also arbitrarily change the order of your commits by dragging and dropping them, which rebases everything to change the order.
+## Setting up the GitLab Integration
-
+In order to set up the GitLab integration, you will need two things. Firstly, a GitLab Personal Token and the Project ID for your project.
-## Edit Mode
+### Creating a GitLab Personal Token
-The other way that you can modify a commit is to go into "Edit Mode". When you click on a commit, there is a button that says "Edit patch". If you click this, GitButler will check out that commit by itself into your working directory (automatically stashing everything else temporarily).
+To create a GitLab Personal Token, navigate to your GitLab preferences, and then click on the "Access tokens" tab. You will then see an "Add new token" button. Pressing this button will provide you with a form (as captured below) where you can specify the token name, expiration date, and scopes. We currently require the `api` scope. In this example, I've chosen to set the expiration date for the token to a year from now.
-The screen will go into "Edit mode", indicating that you're in a special state where you're focusing on this one commit.
+
-
+On completion, you will be showen the following screen which contains your GitLab Personal token.
-Then you can change whatever you want and when you click "Save and exit", it will amend the commit you were editing and rebase anything on top of it.
+
-This is useful for things like getting feedback on a series and being able to go into the appropriate commit, make the changes and continue, as opposed to squashing work.
+### Finding your GitLab Project ID
+Navigate to the main page of the repository you want to configure the integration for. On the top left next to the "Fork" button, you will see three dots. Click on those three dots, and click on "Copy project ID". This will copy the GitLab Project ID to your clipboard.
-# integration-branch.mdx
+
+### Configuring GitButler
-Bundling all your virtual branches together
+Inside GitButler, navigate to the project settings by clicking on the small cog icon in the bottom left. Scroll down to the bottom of the "Project" tab where you will see a form for entering your GitLab details. The provided GitLab Personal Token will be stored securly in your operating system's keychain.
-Since GitButler does some pretty fun stuff with branches in order to enable virtual branches to work, some Git commands run from other git clients, including stock Git in the terminal are bound to behave a little strangely.
+
-We're getting the git data to think in three dimensions, then asking 2-D Git how to deal with it.
+### Custom GitLab Instances
-While some commands cannot work well because of this single-branch limitation (commit, checkout), we do try our best to keep everything in a state where most other commands work reasonably well. Anything having to do with the index or HEAD is a little problematic, but doable in a smooshed state (all branches look like one), while commands like `log` or `blame` work totally fine with no shenanigans.
+You may also provide a different Instance URL if you are using a self-hosted GitLab instance.
-## The Integration Commit
-The way that we handle this relatively well is by creating an "integration" commit every time you change the committed state of your collective virtual branches.
+Note that if you use a custom GitLab instance, you will likely need to configure a custom CSP (Content Security Policy) to allow GitButler to connect to it. You can find more information on how to do that in the [Custom Content Security Policy (CSP)](/troubleshooting/custom-csp) section of the documentation.
-
+## Usage
+You will now have a "Submit for Review" button on each branch which you can use to create a Merge Request.
-So what is an "integration" commit? Well, when you apply or unapply branches, or you commit on one of your applied branches, you change the state of what GitButler sees as your overall committed state with regards to your working directory.
+
-## Status, Diff and Log
+Once the Merge Request is created you will be able to see the status of it from within the client. Please note that we do not yet integrate with CI status.
-To keep Git command output for things that look at the index and HEAD (such as `status` or `diff`) somewhat sane, we modify your index to look like the union of all the committed states of all your applied virtual branches. This makes `git diff` and `git status` behave more or less like you would expect.
+
-For instance, if you have two files on Branch A and two files on Branch B, then `git status` will simply list four files as modified.
-However, to help out, we also write out a branch with a custom commit message that tries to explain the state of things and what is happening. This is written to a branch we own called `gitbutler/workspace` and you shouldn't touch it.
+# ai-assistance.mdx
-If you run `git log`, the first commit should be our custom commit message and the tree of that commit is the union of all the committed work on all your applied virtual branches, as though they were all merged together into one (something stock Git can understand).
+## Getting Started
-## Committing, Branching, Checking Out
+### Global AI Setup (One-time)
-However, if you try to use something that writes to HEAD, like `git commit` or `git checkout`, then you might have some headaches. By default, our client will simply overwrite the `gitbutler/workspace` branch commit whenever something significant changes.
+1. Navigate to **Global settings** → **AI Options**
+2. Choose your AI provider:
+ - **GitButler API** (default): Uses OpenAI through GitButler's servers - no API key needed
+ - **Your own key**: Bring your own OpenAI, Claude, Ollama, or LM Studio credentials
+3. If using your own key, enter your API credentials
-We won't touch the working directory in an unexpected way, so whatever you commit won't be lost, but the commit itself will be forgotten. Don't do branch stuff in stock Git while trying to use GitButler for now. We have ideas on how to make this somewhat doable in the future, but right now it's easier on everyone to stick with one or the other.
+### Per-Project Setup
-## Git Add and the Index
+1. Open **Project settings** → **AI options**
+2. Enable **"Enable branch and commit message generation"**
+3. Optionally enable **"Enable experimental AI features"** for advanced functionality
-If you attempt to modify the index directly (running `git add` or `git rm`), GitButler won't notice or care. It will simply overwrite it with whatever it needs during its operations, so while I wouldn't do it, there is also not a lot of danger.
+## Features
-The worst that would happen is that you do some complex `git add -i` patch staging and then we wipe it out by rewriting the index. But again, you shouldn't be using stock Git commands related to committing or branching. You gotta choose one or the other for now, you can't go back and forth super easily.
+### Branch Name Generation
-## Recovering or Stopping GitButler Usage
+Automatically creates descriptive, kebab-case branch names based on your code changes.
-If you want to stop using GitButler and go back to using stock Git commands for committing and branching, simply check out another branch. GitButler will realize that you've changed its branch and stop functioning until you reset it.
+**Usage:**
-To help with remembering where you were, the integration commit should have the branch name and commit SHA that you were on when GitButler was initially activated. You should be able to easily go back to that branch and its last known commit state.
+- Right-click on a branch header and select **Generate branch name**
+- GitButler analyzes commit messages in the branch and suggests an appropriate name
+- Generated names use kebab-case format and avoid conflicts with existing branches
+### Commit Message Generation
-# signing-commits.mdx
+Creates professional commit messages following best practices.
+**Features:**
-GitHub and GitLab provide a mechanism to verify signed commits using an uploaded public SSH or GPG key. GitButler can be configured automatically sign all your commits.
+- Semantic prefixes (`feat:`, `fix:`, `refactor:`)
+- 50-character title limit, 72-character body wrap
+- Explains what changed and why
+- Real-time streaming as AI generates the message
+- Based on actual code diffs, not just file names
-Git provides a mechanism to sign your commits with a GPG key or SSH key. This enables other developers to make sure that you were actually the person who committed it, rather than someone else just setting their email to yours and committing it as if they were you.
+**Usage:**
+
+1. Make changes to your files (staging is automatic in GitButler)
+2. Click the **Generate message** button in the commit message editor
+3. AI streams the generated message in real-time
+4. Review and edit before committing
+
+**Example format:**
+
+```
+feat: add user authentication system
+
+Implements JWT-based authentication with login and registration
+endpoints. Includes password hashing and session management
+to secure user accounts.
+```
+
+### Pull Request Descriptions
+
+Generates comprehensive PR descriptions when creating pull requests.
+
+**Usage:**
+
+1. Create a pull request from your branch
+2. In the PR creation dialog, click **Generate PR description**
+3. AI analyzes all commits in your branch and generates a description
+4. Review and edit the generated content before creating the PR
+
+**Generated content includes:**
+
+- High-level summary based on commit messages
+- Structured description following PR templates (if configured)
+- Context derived from the changes in your branch
+
+## Advanced Features
+
+### Custom Prompts
+
+- **Global Prompts**: Create custom prompts in **Global settings** → **AI Options**
+- **Project-Specific Prompts**: Assign specific prompts per project in **Project settings** → **AI options**
+- **Commit & Branch Prompts**: Separate customization for commit messages and branch names
+
+## Configuration
+
+### Global Settings (Global settings → AI Options)
+
+- **AI Provider**: Choose between OpenAI, Anthropic, Ollama, or LM Studio
+- **Key Options**: Use GitButler API or bring your own credentials for each provider
+- **Model Selection**: Choose specific models per provider (GPT-4o, Claude Sonnet, etc.)
+- **Amount of provided context**: Set how many characters of git diff to send to AI
+
+### Project Settings (Project settings → AI options)
+
+- **Enable branch and commit message generation**: Master toggle for AI features in this project
+- **Enable experimental AI features**: Access to advanced AI functionality (requires GitButler API)
+- **Custom prompts**: Assign specific prompts from global settings to this project for commits and branches
+
+## Troubleshooting
+
+**AI features not working?**
+
+1. **Check Global Settings**: Navigate to **Global settings** → **AI Options** and verify:
+ - AI provider is configured (OpenAI, Anthropic, etc.)
+ - Key option is selected (GitButler API or your own key)
+ - If using your own key, ensure it's entered correctly
+ - Model is selected for your chosen provider
+2. **Check Project Settings**: Open **Project settings** → **AI options** and ensure:
+ - **"Enable branch and commit message generation"** is turned ON
+ - This setting must be enabled for each project individually
+3. **Verify API Access**: Ensure sufficient API quota and valid credentials
+
+**AI buttons not appearing?**
+
+- The project-level toggle in **Project settings** → **AI options** controls button visibility
+- Without this enabled, Generate buttons won't appear in the UI
+
+**Need better suggestions?**
+
+- Customize prompt templates in **Global settings** → **AI Options**
+- Make meaningful code changes with clear patterns
+- Use descriptive variable names and comments in your code
+- Review [troubleshooting guide](https://docs.gitbutler.com/troubleshooting/custom-csp) for advanced configurations
+
+
+# branch-lanes.mdx
+
+
+All of your branches - remote, local, and virtual / applied or not - are managed in the Branch Tab. This is where you can see all of your branches, apply them to your workspace, and manage your virtual branches.
+
+You can access the Branches tab by clicking on the "Branches" icon in the sidebar.
+
+The interface looks something like this:
+
+
+
+## Branch List
+
+The first pane on the left shows you the virtual branches and stacks that you have as well as the other branches that you have available (legacy git branches, remote branches and PRs).
+
+All of these branches can be converted into virtual branches by clicking them and then clicking the "Apply to workspace" button on the top of the branch view (middle pane).
+
+Local branches can also be fully deleted here.
+
+### Current Workspace Target
+
+The "Current workspace target" is the view of the target branch that you've set. It will show you essentially a `git log` of `origin/master` or whatever you set as your target branch, and it will show you if there are any commits upstream that you have not integrated locally yet. We will automatically check for new upstream changes every few minutes, but you can also click the update button to check immediately.
+
+
+
+
+# index.mdx
+
+## Overview
+
+GitButler is a new Source Code Management system designed to manage your branches, record and backup your work, be your Git client, help with your code and much more. Our focus is everything after writing code in your editor and before sharing it on GitHub. We're focused on your working directory and how we can help with everything you do there.
+
+
+
+Here you will find documentation on the product and the way that we're running our beta and building our product with your help.
+
+## Getting Started
+
+Check out our [Getting Started](/guide) guide to get started with GitButler, or check out our helpful video overview:
+
+https://www.youtube.com/watch?v=DhJtNNhCNLM
+
+## Why We're Doing This
+
+Read about it over [Why GitButler](/why-gitbutler) Section.
+
+## What We Do
+
+The GitButler client is a powerful Git client. You can manage your branches, work on multiple things at once, push and fetch from your Git server, easily rebase and modify commits and more. We have a unique approach to merge conflicts that help split up any conflicting work. It also keeps a timeline so that you can easily undo any operation.
+
+- [Virtual Branches](/features/branch-management/virtual-branches)
+- [First Class Conflicts](/features/branch-management/merging)
+- [Project History](/features/timeline)
+
+
+
+# merging.mdx
+
+
+By default, GitButler rebases the work on your virtual branches when you update your target branch (upstream) work.
+
+Often this works just fine and the commits are simply rebased. Occasionally, you will have conflicts with upstream work.
+
+In this case, GitButler will not do what Git normally does, which is to stop at each conflicted commit and make you fix it before moving on. Instead, it will apply the changes that it can and store the commit as a "conflicted" commit and continue the rebasing process.
+
+When you go to update from upstream, GitButler will show you all the branches that it will rebase and will let you know if any of them will have conflicts:
+
+
+
+In this case, when you perform the rebase, that branch will then contain "conflicted" commits. They will be marked in the UI as conflicted and you can click on them to get a "resolve conflict" button to start the resolution process.
+
+
+
+When you click that, GitButler will remove the other virtual branches and other work from your working directory and check out just this commit with it's conflict markers. It will show you a special "edit mode" screen, where you are directly editing this commit.
+
+
+
+If you want to cancel this conflict resolution, you can just hit 'Cancel' and it will go back to your normal state. If you have fixed all the issues, you can click "Save and Exit" and it will commit the conflict resolution and if needed, rebase any further commits on that branch on top of your new work.
+
+
+
+
+# commits.mdx
+
+
+GitButler gives you a lot of tooling for creating, modifying, squashing, splitting and undoing commits.
+
+GitButler has lots of ways to craft the exact commits that you want to end up with. With other Git clients, you tend to have to run somewhat complicated `git rebase -i` type commands to change commit messages, split a commit up or absorb new changes into an existing commit. With GitButler, most of these are simply drag-and-drop operations.
+
+Here are some of the cool things you can do very easily with GitButler.
+
+## Creating Commits
+
+Once you have changes on a virtual branch and want to commit them, you can hit the "Start a Commit" button in any lane, which gives you an editor to write a summary and optional description for your commit message.
+
+
+
+If you want AI to use your diff to generate a commit message, you can hit the "Generate message" button.
+
+## AI Commit Message Settings
+
+If you want to use AI for generating your commit messages or branch names from time to time, there are quite a few options in your user preferences. You can choose from [OpenAI](https://platform.openai.com/), [Anthropic](https://www.anthropic.com/), [Ollama](https://www.ollama.com/) or [LM Studio](https://lmstudio.ai/) as your engine.
+
+For both OpenAI and Anthropic, you can either use your own API key to directly send your request to their servers, or you can proxy via our server (which you need to be logged in for).
+
+
+
+If you use your own key for OpenAI or Anthropic, you can choose which model you would like us to use.
+
+
+
+If you don't want to send your diff to another server, you can also use Ollama or LM Studio, which are a local LLM servers.
+
+With Ollama, you can run nearly any open source large language model ([Llama 3](https://www.ollama.com/library/llama3), [Phi 3](https://www.ollama.com/library/phi3), [Mistral](https://www.ollama.com/library/mistral), [Gemma](https://www.ollama.com/library/gemma), etc) entirely locally.
+
+Note that if you choose to configure a self-hosted Ollama server, you will likely need to add a custom CSP (Content Security Policy) to allow GitButler to connect to it.
+
+You can find more information on how to do that in the [Custom Content Security Policy (CSP)](/troubleshooting/custom-csp) section of the documentation.
+
+With all of these models, you can also customize the prompt if you want something more specific. In the "Custom AI prompts" section, you can add new prompts and select which one you want to use per project. This is useful for following certain formats or generating messages in other languages, etc.
+
+
+
+Custom prompts can contain three variables which we will replace with the appropriate values. Those include:
+
+- `%{emoji_style}` - Instructs the LLM whether or not to make use of [GitMoji](https://gitmoji.dev) in the title prefix, based on your settings.
+- `%{brief_style}` - Instructs the LLM to not exceed 1 sentence when generating the commit message.
+- `%{diff}` - The contents of the diff.
+- `%{branch_name}` - The name of the current branch. Available in "Commit Message" custom prompt only.
+
+## Absorbing New Work
+
+If you have a commit and get some feedback on it or find an issue and wish to amend it, you can very easily absorb changes into existing commits. Simply drag the file into the commit you want to absorb that change into and drop it there.
+
+This will both rewrite that commit to include the new changes and also rebase every commit upstream from it automatically.
+
+
+
+## Undoing Commits
+
+You can easily undo any commit in your stack by expanding the commit and hitting the 'Undo' button. This will rebase all the commits above it and leave whatever work was in that commit as new uncommitted changes.
+
+
+ "Undo"ing a commit does not throw it away, it simply makes that work not in a commit anymore. It
+ will not discard the changes.
+
+
+
+
+## Undoing One File in a Commit
+
+If you want to undo a single file in a commit, you can expand the commit and click on the file you want to undo. Then hit "Uncommit". This will remove that file from the commit and leave it as uncommitted changes.
+
+
+
+## Squashing Commits
+
+Squashing two commits into a single combined commit is also very simple. Just drag one commit on top of another one.
+
+
+
+## Splitting Commits
+
+Splitting commits is slightly more complex. GitButler allows you to create an "empty" commit anywhere and then drag changes into it. Here is an example of creating an empty commit between two other commits, dragging changes from both of them into it and then absorbing new work into it as well.
+
+
+
+You can also notice that I easily edit the commit message by just hitting the "edit message" button.
+
+## Moving Commits
+
+You can also arbitrarily change the order of your commits by dragging and dropping them, which rebases everything to change the order.
+
+
+
+## Edit Mode
+
+The other way that you can modify a commit is to go into "Edit Mode". When you click on a commit, there is a button that says "Edit patch". If you click this, GitButler will check out that commit by itself into your working directory (automatically stashing everything else temporarily).
+
+The screen will go into "Edit mode", indicating that you're in a special state where you're focusing on this one commit.
+
+
+
+Then you can change whatever you want and when you click "Save and exit", it will amend the commit you were editing and rebase anything on top of it.
+
+This is useful for things like getting feedback on a series and being able to go into the appropriate commit, make the changes and continue, as opposed to squashing work.
+
+
+# pushing-and-fetching.mdx
+
+
+GitButler can authenticate with an upstream Git server in several different ways.
+
+ You can just tell us to use the system Git executable, which you can setup however you want. You can use our built in SSH protocol with your own SSH key (this does not require you to have Git installed), or you can use the default [Git credentials helper](https://git-scm.com/doc/credential-helpers).
+
+You can set your preference (and test if it works) in your project's "Git authentication" section:
+
+
+
+Once that's done, GitButler will be able to automatically fetch upstream work and push new branches to your upstream server.
+
+
+# signing-commits.mdx
+
+
+GitHub and GitLab provide a mechanism to verify signed commits using an uploaded public SSH or GPG key. GitButler can be configured automatically sign all your commits.
+
+Git provides a mechanism to sign your commits with a GPG key or SSH key. This enables other developers to make sure that you were actually the person who committed it, rather than someone else just setting their email to yours and committing it as if they were you.
To make this work, a signature is added to the commit header and then that signature is checked against public key stored somewhere, generally for most people the most useful way to verify these signatures is through GitHub or GitLab.
-This is what a verified commit looks like on both systems:
+This is what a verified commit looks like on both systems:
+
+
+
+
+
+This means that the server has a public key that you used to sign the commits that is associated to your account and has verified that this user actually signed this commit.
+
+In order for this to work, you need to:
+
+1. Tell GitButler to sign your commits
+2. Upload your key as a "signing key" to GitHub or GitLab (or elsewhere)
+
+## Telling GitButler to Sign
+
+For GitButler to sign commits, you need to setup Git to sign commits, as we do roughly the same thing that Git itself tries to do, and we read and respect most of the same Git config settings.
+
+The main difference is that instead of only the `commit.gpgSign` as the flag that tells Git to automatically sign commits, we look for `gitbutler.signCommits` first. Thus, if Git would sign, GitButler will attempt to sign your commits with the normal Git settings as well.
+But if something goes wrong, `gitbutler.signCommits` will be set to `false` in the repository-local settings to prevent commits from
+failing generally.
+
+We look to see if we have a signing key in `user.signingkey`. If we have a key, we look for 'ssh' in `gpg.format`, otherwise we use GPG. We will respect `gpg.ssh.program` for ssh if there is a different binary path, and `gpg.program` for GPG. We also identify literal SSH keys in the `user.signingkey` field.
+
+The only major thing we don't support yet is `gpg.ssh.defaultKeyCommand` for other ways to get a key other than the `user.signingkey` field. We also don't support the X.509 smime stuff.
+
+Here is an example, if you have a public key here `.ssh/id_ed25519.pub` then you can setup signing with something like this:
+
+```bash title="Terminal"
+$ git config --global user.signingkey "/Users/schacon/.ssh/id_ed25519.pub"
+$ git config --global gpg.format ssh
+$ git config --global gitbutler.signCommits true
+```
+
+You can also set this up in your project settings, perhaps a little more easily:
+
+
+
+The nice thing here is that you can also test the settings easily by hitting the "Test Signing" button.
+
+There are lots of other ways to set up GPG or SSH commit signing:
+
+- 1Password is a very easy way to [SSH sign commits](https://blog.1password.com/git-commit-signing/).
+- GitHub has a [good guide](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) on how to setup GPG or SSH keysigning.
+- Here is a nice simple gist for [GPG signing on Windows](https://gist.github.com/BoGnY/f9b1be6393234537c3e247f33e74094a).
+
+
+Earlier versions of GitButler would only sign with it's generated SSH key. Although we've removed that functionality, you can easily set it back up by pointing the signingKey at the generated SSH Key. The key is located in the following locations:
+
+
+
+ ```bash
+ /Users/[username]/Library/Application Support/com.gitbutler.app/keys/ed25519.pub
+ ```
+
+
+ ```bash
+ C:\Users\[username]\AppData\Roaming\com.gitbutler.app\keys\ed25519.pub
+ ```
+
+
+ ```bash
+ ~/.local/share/gitbutler-tauri/keys/ed25519.pub
+ ```
+
+
+
+
+
+## Upload Your Signing Key
+
+For GitHub or GitLab to verify your signatures, you need to say that the SSH or GPG public key we are using is a valid signing key for your user.
+
+### Adding to GitHub
+
+You can click on the "Add key to GitHub" link in the settings page right about the signing toggle, or you can go here (https://github.com/settings/ssh/new) to paste that public key in.
+
+
+
+Now your signed commits should show up as "Verified".
+
+### Adding to GitLab
+
+For GitLab you need to go to "SSH Keys" in your profile: https://gitlab.com/-/profile/keys and click the "Add new key" button.
+
+
+
+Now paste in the public SSH key you copied from GitButler, name it and make sure the "Usage Type" is either "Signing" or "Authentication and Signing".
+
+
+
+
+# upstream-integration.mdx
+
+Sometimes you work on a branch and someone else pushes to the same upstream branch. Often you won't know this until you try to push and Git tells you something like this:
+
+
+
+In this scenario, GitButler gives you some nice tooling to help you know when this happens as early as possible and help you deal with it easily.
+
+If someone else has pushed to a branch that you're working on, you will see the upstream commits without having to integrate them. You can look at the commits without having to merge them into your branch or rebase your work on top of them.
+
+
+
+When you decide that you do want to integrate the changes, you have two options - rebase or interactively integrate.
+
+## Rebase the changes
+
+If you select "Rebase upstream changes", it will do the equivalent of a `git pull --rebase` which rebases the commits you have locally on top of the ones that the other person has pushed, so you end up with a state like this:
+
+
+
+Now you can push your commit back upstream without a force push. Easy peasy.
+
+## Interactively integrate the changes
+
+However, let's say that you want to do something more complex. Maybe the other implemented the same thing that you did and you want to drop one of them or one of yours, or squash commits together or reorder them. In any of these cases, you can choose the "Interactive integration" option and you get something that looks like this:
+
+
+
+Here you can reorder commits however you want, you can choose to skip some of them, you can squash some of them down, etc. Just make the commits look however you prefer and then hit the "Integrate changes" button and push your final result back to the server.
+
+
+# virtual-branches.mdx
+
+
+Virtual branches are a powerful feature of GitButler that allow you to work on multiple branches at the same time, committing to them independently and simultaneously. This is a key part of the GitButler experience, allowing you to manage your work in a flexible and efficient way that is not possible with traditional Git tooling.
+
+## Overview
+
+With normal Git branching, you can only work on one branch at a time. There is one `HEAD` reference and one index.
+
+With virtual branches, you can have multiple branches applied to your working directory at the same time. Each branch is represented as a vertical lane, and you can drag changes between these lanes to commit them independently.
+
+Each lane also has it's own staging area, so you can stage changes for each branch before deciding to commit them.
+
+
+
+## How it works
+
+Let's say that you make changes to two different files and `git status` would list two modified files. In GitButler, you can "assign" the change in each file to a different "virtual" branch, then when you commit, it will create a commit that only contains the changes in that file for that branch.
+
+One of the nice things with this approach is that since you're starting from changes in a single working directory, you can be sure that all branches that you create from it will merge cleanly, as you're essentially starting from the merge product and extracting branches of work from it.
+
+
+# stacked-branches.mdx
+
+
+Create a stack of dependent branches to be reviewed and merged in order.
+
+## Overview
+
+GitButler allows you to create an ordered stack of branches where each branch depends on (and is based on) the previous one.
+The application also supports creating the appropriate stacked Pull Requests (when used with a GitHub remote).
+This is useful when you have multiple changesets that depend on each other but it is desirable to have them reviewed and merged separately (and in sequence).
+
+> All of the Pull Request stack orchestration is done locally in the client, which means that your repo content is not shared with a cloud service.
+
+
+
+## Use cases
+
+Using stacked branches (Pull Requests) can be helpful for shipping smaller changes more frequently.
+
+### Breaking up a larger change into smaller ones
+
+Consider a scenario where you are implementing a medium/large feature in your software project.
+In the course of implementation you end up performing the following sub-tasks:
+
+1. Refactor a part of the codebase to accommodate the new feature
+2. Implement an API endpoint supporting the feature
+3. Implement the frontend part of the feature consuming the API
+
+While the feature is considered complete only when all of the subtasks are implemented, reviewed and merged, in many cases it is considered beneficial
+to ship each stage of the feature on its own, potentially behind a feature flag. Not only the risk of merge conflicts with colleagues is reduced,
+but also eventual bugs are easier to track down / revert / fix as compared to a single large change.
+
+### More granular (easier) review process
+
+On GitHub at least, code reviews are performed on per-branch basis. While it is possible to view individual commits in a Pull Request, it is not possible to
+approve and merge a subset of commits from the PR.
+
+Utilizing stacked pull requests, means that the sub-tasks of a larger change are in their own PRs.
+This way it is possible to approve and merge the initial part of a stack (e.g. a refactor) while still iterating on the remaining sub-tasks.
+
+## Comparison to Virtual Branches
+
+Stacking and Virtual Branches are similar in that they allow you to separate code changes / commits into different branches. In both cases,
+the changes are available in your working directory.
+
+The main difference is that Virtual Branches are **independent** from one another, while stacked branches **depend** on the ones that come before it.
+Because of this, the two features are not mutually exclusive but rather complementary. For example a bugfix change that is unrelated to a feature
+can be put in a separate virtual branch. On the other hand, a change that depends on a previous change can be put in a stacked branch above the one it depends on.
+
+In fact GitButler implements stacked branches as Virtual Branches that are split into multiple dependent branches.
+
+
+
+## Workflow
+
+By default, virtual branches in the app are simply stacks of one.
+With version `0.14.0` or newer you can create a new dependent branch within a lane by clicking the `+` button above the branch name.
+
+> The workflow below assumes a GitHub remote. If you are using a different forge, you can still use this functionality but will need to manually create/update the Pull/Merge Requests
+
+1. Creating a new dependent branch forms a stack within the lane.
+
+
+
+2. New commits land in the top branch of the stack.
+
+
+
+3. Pushing is done for the stack as a whole. Note: The Pull Requests will be created in a way where each branch points to its parent - see [Automatic branch deletion](#automatic-branch-deletion)
+
+
+
+4. Pull requests must be created one at a time starting from the bottom of the stack.
+
+
+
+5. The PRs will contain a footer with stack information, and as you add more PRs it will keep all up to date.
+
+
+
+6. You can drag changes into commits to amend them (e.g. incorporating review feedback) as well as move and squash commits.
+
+
+
+
+
+7. If a change in your stack is independent (e.g. an unrelated bugfix) it can be moved to a different virtual branch (or stack).
+ This works for both uncommitted changes and existing commits that you may want to relocate.
+
+
+
+8. Review/merge your PRs starting from the bottom up. After a PR/branch from your stack has been merged, it is reflected in the Stack and you should force push to reflect the changes
+ on the remote as well.
+
+
+
+
+9. When all branches of a stack have been merged, the stack is complete.
+
+## GitHub configuration for stacked PRs
+
+_TLDR:_
+
+1. Enable automatic branch deletion [automatic branch deletion](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-the-automatic-deletion-of-branches)
+ on GitHub.
+2. If possible, consider using the the "Merge" strategy when merging PRs.
+
+#### Automatic branch deletion
+
+When reviewing a PR in a stack, it is important to be able to view only the changes in the branch that is being reviewed.
+Of course, in pure Git terms, a stacked branch will contain all the changes from the branches below it.
+
+In order to show only the expected Files changed and Commits for PRs in a stack, each PR is created to target the branch below it in the stack.
+This is true for all but the bottom branch in the stack, which targets the default branch of the repository as usual.
+
+
+
+> Every branch in the stack contains the commits from the branches below it.
+
+This of course does not mean that a Pull Request should be merged into its parent.
+When the bottom branch is merged on GitHub, **if** the PR branch is deleted,
+GitHub will automatically update any PRs that used to target it to target the default branch instead.
+
+
+
+If the newly merged branch from the bottom of the stack is not deleted, the next PR in line will still target it and there is a risk of accidentally merging it into the now out of date branch.
+For this reason it is _highly recommended_ to [enable on GitHub](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-the-automatic-deletion-of-branches) the automatic deletion of branches after merging.
+
+_NB:_ If you merge the first PR but the branch is not deleted and then merge the second PR,
+the app can still recover from this, see [Troubleshooting](#troubleshooting).
+
+#### Merge strategy
+
+The app will support any merge strategy you wish to use - "Merge", "Rebase" or "Squash". However, due to the nature of merge,
+the GitButler will be able to create a slightly better experience if the "Merge" strategy is used.
+The reason for this is with merge commits you will be able to merge all the branches in the stack from GitHub without having to force push in the app.
+
+## Troubleshooting
+
+Firstly, if you run into any issue with the app (stacking or not), you can always get in touch either
+on [Discord](https://discord.com/invite/MmFkmaJ42D) or via the in-app feedback icon (we will get back via email).
+With that said, here are some workarounds for common issues.
+
+#### Accidentally merged a stack branch into an already merged branch before it
+
+If you merged the bottom Pull Request into main but the branch was _not_ deleted, then the target of the next Pull Request would not be automatically updated.
+Under these conditions merging that next Pull Request, means it would be merged into the original, now out of date, bottom PR.
+
+A mitigation for this is to rename the branch, push and re-create the Pull Request.
+
+#### Accidentally merged a branch into a branch before it (not integrated into main/master yet)
+
+Merging of branches in a stack should be done from the bottom up. With the GitHub interface, it is possible to incorrectly merge a Pull Request which is in the middle of the stack.
+In this case it will merged in the parent branch.
+
+In order to recover from this situation you can simply force push the branches and then re-create the PR that was incorrectly merged.
+
+
+# ai-overview.mdx
+
+
+If you're using AI agent tools like Cursor, Windsurf, or Claude Code, GitButler can enhance your coding experience by managing commits, saving points, and more. These integrations allow you to focus on coding with your agents while GitButler handles the version control aspects.
+
+https://www.youtube.com/watch?v=J6xV_Wyz9zg
+
+There are currently three main ways to use AI tools with GitButler:
+
+1. **Our Agents Tab**: If you have Claude Code setup, you can use our [Agents tab](/features/agents-tab) as a GUI for running Claude Code directly.
+2. **Using Hooks in Claude Code or Cursor**: This method allows you to use GitButler's CLI as hook commands to manage commits and branches in either Claude Code or Cursor.
+3. **Using the MCP Server**: This method allows you to set up your AI agent to communicate with GitButler's MCP server, enabling features like automatic commits and save points.
+
+## Enabling the experimental feature flag
+
+Note that as of GitButler version `0.15.2` these features have to be enabled via an experimental feature flag. You can find that under `Global Settings` -> `Experimental` -> `GitButler Actions`.
+
+
+# cursor-hooks.mdx
+
+GitButler integrates seamlessly with Cursor through hooks that automatically manage your commits and branches while you're using AI coding features. This allows you to automatically maintain clean git history and organized virtual branches.
+
+Here's a short video showing how GitButler works with Cursor hooks:
+
+https://youtu.be/NOYK7LTFvZM
+
+Ok, let's get it set up.
+
+## Install the GitButler CLI
+
+First, you need to install the GitButler CLI, which can be done in your General settings. See the [MCP Server documentation](mcp-server) for more details on how to install the CLI.
+
+## Installing GitButler as a Hook
+
+Once the command line tool is installed, you can add the `but cursor` commands as hooks.
+
+You will need to create or edit your `~/.cursor/hooks.json` file (globally) or `[project]/.cursor/hooks.json` file (single project) to have `afterFileEdit` and `stop` hooks like this:
+
+```json
+{
+ "version": 1,
+ "hooks": {
+ "afterFileEdit": [
+ {
+ "command": "but cursor after-edit"
+ }
+ ],
+ "stop": [
+ {
+ "command": "but cursor stop"
+ }
+ ]
+ }
+}
+```
+
+## Using GitButler with Cursor
+
+Once the hooks are setup, Cursor will automatically call GitButler when it edits files and when it's done with a task, which will trigger GitButler to:
+
+- Create a branch if the chat session is new
+- Assign edits to an active branch
+- Commit with a message based on the prompt when a task is done
+
+
+# mcp-server.mdx
+
+
+If you use an AI agent (such as Cursor, Windsurf, Claude Code) to help you with your code, you can easily setup GitButler to manage your commits automatically, keep save points, and more. You know, _vibe_ commit...
+
+## Setting up your Agent to use GitButler
+
+The first step is to let your agent know about GitButler, which is done via MCP - you need to tell your agent to use the GitButler MCP server.
+
+### Installing the CLI
+
+GitButler provides a CLI that can be used to interact with the GitButler platform. Before you can setup AI Agent integration, you will need to install the CLI.
+
+This can be found by opening the GitButler global settings, and then clicking on the "Install CLI" button in the General settings.
+
+
+
+Now that you have the `but` CLI installed, your agent can use the CLI's MCP server to interact with GitButler.
+
+### Cursor
+
+To install the GitButler MCP server in Cursor, first go to the Cursor settings, and then click on the "Extensions" tab, then click on "Tools and Integrations" and click on "New MCP Server".
+
+This will open your `~/.cursor/mcp.json` file.
+
+Add the following to the `mcpServers` object:
+
+```json
+{
+ "mcpServers": {
+ "gitbutler": {
+ "command": "but",
+ "args": ["mcp"]
+ }
+ }
+}
+```
+
+You should see the GitButler MCP server in the list of MCP servers and it should have the tool `gitbutler_update_branches` available.
+
+### VSCode
+
+To install the GitButler MCP server in VSCode, you need to select "MCP: List Servers" from the actions menu. Then select "Add Server". Select "stdio" as the server type.
+
+Now you can type your command (`but mcp`) and name it something. After this, it should open up your settings file and show you something like this:
+
+```json
+ "mcp": {
+ "servers": {
+ Running | Stop | Restart | 1 tools
+ "gitbutler-mcp": {
+ "type": "stdio",
+ "command": "but",
+ "args": ["mcp"]
+ }
+ }
+ }
+```
+
+However, if you have Cursor's MCP already setup, VSCode will notice and help you automatically reuse the settings.
+
+
+
+### Claude Code
+
+Adding an MCP server to Claude Code is done by running the `claude mcp add` command.
+
+```
+❯ claude mcp add gitbutler but mcp
+Added stdio MCP server gitbutler with command: but mcp to local config
+
+❯ claude mcp list
+gitbutler: but mcp
+```
+
+## Rules: How to configure auto committing
+
+Once you have installed the MCP server in your editor or agent, you can optionally configure it to automatically commit your changes.
+
+We've found that adding something like this to your rules works well:
+
+```
+If you generate code or modify files, run the gitbutler update branches MCP tool.
+```
+
+## How to add rules
+
+Cursor stores it's rules in `~/.cursor/rules` file, but you can also manually set them by going to the Cursor Settings pane, clicking 'Rules' and adding them to the User Rules section.
+
+In VSCode's Copilot Agent Mode, you can use ["custom instructions"](https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions) to accomplish this.
+
+In Claude Code, they are now called "memories" and you can add them by hitting '#' and storing them in user memory (or local if you just want them in one project).
+
+
+
+Or directly in your `~/.claude/CLAUDE.md` rules file:
+
+```
+❯ cat ~/.claude/CLAUDE.md
+## Development Workflow
+- When you're done with a task where code was created or files edited, please run the gitbutler mcp update_branches command.
+```
+
+## Using GitButler with your agent
+
+If you've set up a rule/instruction/memory, then every time a chat session is completed, the agent will send the changes and prompt to GitButler and it will automatically commit the changes.
+
+
+
+If you're using Claude Code, it may look something like this:
+
+
+
+If you don't have the agent setup to automatically call our tool, then you can also just manually type 'update gitbutler branches' in the chat, but that's a little less magical.
+
+## GitButler interface
+
+There are two phases to GitButler's MCP agent interaction. The first is the agent sending the changes and prompt to GitButler, which GitButler will quickly record and then return a success to the agent. The second is GitButler processing that raw recorded change and attempting to process that change into a commit.
+
+### Recording the changes
+
+When your agent calls the `gitbutler_update_branches` tool, GitButler will record the changes and prompt and then immediately return to the agent, so the call should be very fast.
+
+So for instance, let's say that I prompted my coding agent to update my `README.md` file to add a list of contributing authors. When the agent is done, it should call the update branches MCP tool, which will record a commit that looks something like this:
+
+
+
+### Processing the changes
+
+Then, if you have AI tooling setup, GitButler will see that and turn it into a commit message like this:
+
+
+
+You can see all of these steps in the "Actions" section of the GitButler interface, which you can toggle by hitting the "Actions" button in the top right of the interface.
+
+
+
+In the near future, we will also be able to do more interesting things like auto-absorbing changes into existing commits, creating new branches based on the prompt theme, creating stacked branches, and more.
+
+
+# contact-us.mdx
+
+
+There are a few ways to get in touch with us for feedback, bug reports, feature requests, etc.
+
+
+ }
+ href="mailto:hello@gitbutler.com"
+ title="Email"
+ description="The simplest way to get in touch with us is to email us"
+ />
+ }
+ href="https://discord.com/invite/MmFkmaJ42D"
+ title="Discord"
+ description="We are also available to chat on our Discord server"
+ />
+
+
+
+# claude-code-hooks.mdx
+
+If you are using Claude Code, you can use the new ["hooks"](https://docs.anthropic.com/en/docs/claude-code/hooks) functionality to manage the output of even multiple simultaneous instances, while isolating all the generated code into virtual or stacked branches automatically. In this case, there is no need to set up the MCP server, as the hooks will handle everything for you.
+
+Here's a short video showing how GitButler works with Claude Code:
+
+https://youtu.be/AwwPwSc9qhA
+
+Ok, let's get it set up.
+
+## Install the GitButler CLI
+
+First, you need to install the GitButler CLI, which can be done in your General settings. See the [MCP Server documentation](./mcp-server) for more details on how to install the CLI.
+
+## Installing GitButler as a Hook
+
+Hooks in Claude Code are defined in one of your settings files.
+
+```
+~/.claude/settings.json - User settings
+.claude/settings.json - Project settings
+.claude/settings.local.json - Local project settings (not committed)
+```
+
+Wherever you want to add GitButler to handle your commits automatically, you can add us as a hook by adding the following to the `hooks` array in whatever settings file you want to use:
+
+```json
+{
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "Edit|MultiEdit|Write",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "but claude pre-tool"
+ }
+ ]
+ }
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "Edit|MultiEdit|Write",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "but claude post-tool"
+ }
+ ]
+ }
+ ],
+ "Stop": [
+ {
+ "matcher": "",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "but claude stop"
+ }
+ ]
+ }
+ ]
+ }
+}
+```
+
+Essentially, you want to run the `but claude pre-tool` command before any code generation or editing, and the `but claude post-tool` command after it. The `but claude stop` command will run when you stop the agent, ensuring that all changes are committed and branches are updated accordingly.
+
+You also might want to add to your "memories" to ask Claude not to try commiting using Git as it will be handled by GitButler. You can do something like this:
+
+```
+❯ cat ~/.claude/CLAUDE.md
+## Development Workflow
+- Never use the git commit command after a task is finished.
+```
+
+## Using GitButler with Claude Code
+
+With the hooks setup, Claude will tell GitButler when it has generated code or edited files and in which session, which helps GitButler to try to isolate the changes into a single branch per session.
+
+For example, if you have three sessions of Claude Code running at the same time, each will be communicating with GitButler at each step and GitButler will be assigning each change to the correct branch automatically.
+
+When the agent is done, GitButler will commit all the changes and write a more sophisticated commit message based on what you had prompted your agent.
+
+
+# debugging.mdx
+
+
+If you are having technical issues with the GitButler client, here are a few things you can do to help us help you. Or help yourself.
+
+If you get stuck or need help with anything, hit us up over on Discord, here's [GitButler Discord Server Link](https://discord.gg/MmFkmaJ42D).
+
+
+
+The first things to try is checking out the frontend related logs in the console by opening the developer tools in GitButler via the "View" -> "Developer Tools" menu option. Next, if you launch GitButler from the command line, you can view the backend logs directly in your terminal.
+
+## Logs
+
+Often the most helpful thing is to look at the logs. GitButler is a Tauri app, so the logs are in your OS's [app log directory](https://docs.rs/tauri/1.8.1/tauri/api/path/fn.app_log_dir.html). This should be:
+
+
+
+ ```bash
+ ~/Library/Logs/com.gitbutler.app/
+ ```
+
+
+ ```bash
+ C:\Users\[username]\AppData\Local\com.gitbutler.app\logs
+ ```
+
+
+ ```bash
+ ~/.config/gitbutler/logs/ [OR]
+ ~/.local/share/gitbutler-tauri/logs/
+ ```
+
+
+
+In this directory, there should be rolling daily logs:
+
+
+```bash title="Terminal"
+❯ cd ~/Library/Logs/com.gitbutler.app
+
+❯ tree -L 1
+
+├── GitButler.log
+├── GitButler.log.2023-09-02
+├── GitButler.log.2023-09-03
+├── GitButler.log.2023-09-04
+├── GitButler.log.2023-09-05
+├── GitButler.log.2023-09-06
+├── GitButler.log.2023-09-07
+├── GitButler.log.2023-09-08
+├── GitButler.log.2023-10-10
+├── GitButler.log.2024-01-30
+└── tokio-console
+
+❯ tail GitButler.log.2024-01-30
+2024-01-30T13:02:56.319843Z INFO get_public_key: gitbutler-app/src/keys/commands.rs:20: new
+2024-01-30T13:02:56.320000Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: new key="gitbutler.utmostDiscretion"
+2024-01-30T13:02:56.320117Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: new key="gitbutler.signCommits"
+2024-01-30T13:02:56.320194Z INFO get_public_key: gitbutler-app/src/keys/commands.rs:20: close time.busy=317µs time.idle=47.0µs
+2024-01-30T13:02:56.320224Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: close time.busy=204µs time.idle=25.3µs key="gitbutler.utmostDiscretion"
+2024-01-30T13:02:56.320276Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: close time.busy=133µs time.idle=35.8µs key="gitbutler.signCommits"
+2024-01-30T13:02:56.343467Z INFO menu_item_set_enabled: gitbutler-app/src/menu.rs:11: new menu_item_id="project/settings" enabled=false
+2024-01-30T13:02:56.343524Z INFO menu_item_set_enabled: gitbutler-app/src/menu.rs:11: close time.busy=35.7µs time.idle=28.8µs menu_item_id="project/settings" enabled=false
+```
+
+
+## Data Files
+
+GitButler also keeps it's own data about each of your projects. The virtual branch metadata, your user config stuff, a log of changes in each file, etc. If you want to inspect what GitButler is doing or debug or reset everything, you can go to our data directory.
+
+
+
+ ```bash
+ ~/Library/Application Support/com.gitbutler.app/
+ ```
+
+
+ ```bash
+ C:\Users\[username]\AppData\Roaming\com.gitbutler.app
+ ```
+
+
+ ```bash
+ ~/.local/share/gitbutler-tauri/
+ ```
+
+
+
+In this folder there are a bunch of interesting things.
+
+
+```bash title="Terminal"
+❯ cd ~/Library/Application\ Support/com.gitbutler.app
+
+❯ tree
+.
+├── keys
+│ ├── ed25519
+│ └── ed25519.pub
+├── projects.json
+└── settings.json
+
+4 directories, 4 files
+```
+
+
+The `projects.json` file will have a list of your projects metadata:
+
+
+```bash title="Terminal"
+❯ cat projects.json
+[
+ {
+ "id": "71218b1b-ee2e-4e0f-8393-54f467cd665b",
+ "title": "gitbutler-blog",
+ "description": null,
+ "path": "/Users/scottchacon/projects/gitbutler-blog",
+ "preferred_key": "generated",
+ "ok_with_force_push": true,
+ "api": null,
+ "gitbutler_data_last_fetch": null,
+ "gitbutler_code_push_state": null,
+ "project_data_last_fetch": {
+ "fetched": {
+ "timestamp": {
+ "secs_since_epoch": 1706619724,
+ "nanos_since_epoch": 202467000
+ }
+ }
+ }
+ }
+]
+```
+
+
+The `settings.json` are some top level preferences you've set.
+
+
+```bash title="Terminal"
+❯ cat settings.json
+{
+ "appAnalyticsConfirmed": true,
+ "appNonAnonMetricsEnabled": true
+}
+```
+
+
+Finally, the `keys` directory holds the SSH key that we generate for you in case you don't want to go through creating your own. It's only used if you want to use it to sign commits or use it for authentication.
+
+## Linux
+
+### `glibc` Errors
+
+The Linux installation is currently being built in a GitHub Action with Ubuntu 24.04. This means support is limited to those installations using the same or newer version of `glibc`. Unfortunately we cannot build using earlier versions of Ubuntu due to another incompatibility with `libwebkit2gtk-4.1` and Tauri at the moment.
+
+If you're using an older distribution, you may be interested in trying our Flatpak package available on Flathub.
+
+### `Failed to create EGL image from DMABuf`
+
+If you start GitButler from the command line and see a bunch of these or similar `EGL` / `DMABuf` related messages printed to the console and are only getting a white screen to render, you can try launching GitButler with the following environment variables:
+
+- `WEBKIT_DISABLE_DMABUF_RENDERER=1`
+- `WEBKIT_DISABLE_COMPOSITING_MODE=1`
+
+This issue most likely stems from an incompatibility between your version of OpenGL (`mesa`) and `libwebkit2gtk-4.1`.
+
+
+# supporters.mdx
+
+
+Thinking about paying for Beta software? Sounds odd, right?
+
+No worries, the main stuff in GitButler stays the same whether you pay or not.
+
+But hey, we're all about building a cool gang here. We want to know who really digs our butler. And those early supporters? They're like VIPs to us.
+
+## Perks for Early Supporters
+
+- Access to our Early Bird Discord room, for life
+- Invitations to exclusive Berlin parties, when it's warm here
+- Care packages of schwag, sent your way
+- Pricing locked in, no matter how we decide to charge later
+- First look at any new features as we go
+- Whatever else we can think of over time
+
+Your support helps us grow and make GitButler even better. Join us on this adventure!
+
+## How to Support Us
+You need to have a GitButler account to support us. If you don't have one, sign up first.
+
+
+ }
+ href="https://app.gitbutler.com/supporter"
+ title="GitButler"
+ description="Support GitButler with a monthly contribution"
+ />
+
+
+Thanks, from the GitButler Crew!
+
+
+
+
+
+# but-base.mdx
+
+## Usage
+
+```
+but base
+```
+
+## Subcommands
+
+### check
+
+Fetches remotes from the remote and checks the mergeability of the branches in the workspace.
+
+```
+but base check
+```
+
+Shows the status of the base branch including:
+
+- Base branch name
+- Number of upstream commits
+- Recent commits
+- Status of active branches (updatable, integrated, conflicted, etc.)
+
+### update
+
+Updates the workspace (with all applied branches) to include the latest changes from the base branch.
+
+```
+but base update
+```
+
+Integrates upstream changes into your workspace branches, rebasing or deleting branches as appropriate.
+
+## Examples
+
+Check base branch status:
+
+```
+but base check
+```
+
+Update workspace with base branch changes:
+
+```
+but base update
+```
+
+
+# but-branch.mdx
+
+## Usage
+
+```
+but branch
+```
+
+## Subcommands
+
+### new
+
+Creates a new branch in the workspace.
+
+```
+but branch new [OPTIONS] [BRANCH_NAME]
+```
+
+#### Arguments
+
+- `[BRANCH_NAME]` - Name of the new branch (optional, auto-generated if not provided)
+
+#### Options
+
+- `-a, --anchor ` - Anchor point - either a commit ID or branch name to create the new branch from
+
+### delete
+
+Deletes a branch from the workspace.
+
+```
+but branch delete [OPTIONS]
+```
+
+Alias: `-d`
+
+#### Arguments
+
+- `` - Name of the branch to delete (required)
+
+#### Options
+
+- `-f, --force` - Force deletion without confirmation
+
+### list
+
+List the branches in the repository.
+
+```
+but branch list [OPTIONS]
+```
+
+#### Options
+
+- `-l, --local` - Show only local branches
+
+### unapply
+
+Unapply a branch from the workspace.
+
+```
+but branch unapply [OPTIONS]
+```
+
+#### Arguments
+
+- `` - Name of the branch to unapply (required)
+
+#### Options
+
+- `-f, --force` - Force unapply without confirmation
+
+## Examples
+
+Create a new branch with auto-generated name:
+
+```
+but branch new
+```
+
+Create a new branch with a specific name:
+
+```
+but branch new my-feature
+```
+
+Create a new branch from a specific commit:
+
+```
+but branch new my-feature --anchor abc123
+```
+
+Delete a branch with confirmation:
+
+```
+but branch delete my-feature
+```
+
+Force delete a branch without confirmation:
+
+```
+but branch delete my-feature --force
+```
+
+List all branches:
+
+```
+but branch list
+```
+
+List only local branches:
+
+```
+but branch list --local
+```
+
+Unapply a branch from the workspace:
+
+```
+but branch unapply my-feature
+```
+
+
+# but-commit.mdx
+
+## Usage
+
+```
+but commit [OPTIONS]
+```
+
+## Options
+
+### `-m, --message `
+
+Commit message.
+
+- **Type:** String
+- **Required:** Optional
+
+### `--branch `
+
+Branch CLI ID or name to derive the stack to commit to.
+
+- **Type:** String
+- **Required:** Optional
+
+### `-o, --only`
+
+Only commit assigned files, not unassigned files.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Commit with a message:
+
+```
+but commit -m "Add new feature"
+```
+
+Commit to a specific branch:
+
+```
+but commit -m "Fix bug" --branch my-feature-branch
+```
+
+Commit only assigned files:
+
+```
+but commit -m "Update docs" --only
+```
+
+
+# but-describe.mdx
+
+## Usage
+
+```
+but describe
+```
+
+## Alias
+
+This command can also be invoked using the shorter alias `desc`:
+
+```
+but desc
+```
+
+## Arguments
+
+### ``
+
+Commit ID to edit the message for, or branch ID to rename.
+
+- **Type:** String
+- **Required:** Yes
+
+## Examples
+
+Edit a commit message:
+
+```
+but describe abc123
+```
+
+Rename a branch:
+
+```
+but describe my-feature-branch
+```
+
+
+# but-forge.mdx
+
+## Usage
+
+```
+but forge
+```
+
+## Subcommands
+
+### auth
+
+Authenticate with your forge provider (at the moment, only GitHub is supported).
+
+```
+but forge auth
+```
+
+Initiates a device OAuth flow to authenticate with GitHub. You'll be provided with a code and URL to complete the authentication process.
+
+### list-users
+
+List authenticated forge accounts known to GitButler.
+
+```
+but forge list-users
+```
+
+Displays a list of all GitHub usernames that have been authenticated with GitButler.
+
+### forget
+
+Forget a previously authenticated forge account.
+
+```
+but forge forget
+```
+
+#### Arguments
+
+- `` - The username of the forge account to forget (required)
+
+## Examples
+
+Authenticate with GitHub:
+
+```
+but forge auth
+```
+
+List authenticated users:
+
+```
+but forge list-users
+```
+
+Forget a GitHub account:
+
+```
+but forge forget octocat
+```
+
+
+# but-init.mdx
+
+## Usage
+
+```
+but init [OPTIONS]
+```
+
+## Options
+
+### `-r, --repo`
+
+Also initializes a git repository in the current directory if one does not exist.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Initialize GitButler in an existing git repository:
+
+```
+but init
+```
+
+Initialize both git and GitButler:
+
+```
+but init --repo
+```
+
+
+# but-mark.mdx
+
+## Usage
+
+```
+but mark [OPTIONS]
+```
+
+## Arguments
+
+### ``
+
+The target entity that will be marked.
+
+- **Type:** String
+- **Required:** Yes
+
+## Options
+
+### `-d, --delete`
+
+Deletes a mark.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Create a mark:
+
+```
+but mark my-feature-branch
+```
+
+Delete a mark:
+
+```
+but mark my-feature-branch --delete
+```
+
+## Related Commands
+
+- [`but unmark`](but-unmark) - Removes all marks from the workspace
+
+
+# but-mcp.mdx
+
+## Usage
+
+```
+but mcp [OPTIONS]
+```
+
+## Description
+
+Starts the Model Context Protocol (MCP) server for GitButler, enabling integration with AI assistants and other tools that support the MCP protocol.
+
+## Options
+
+### `-i, --internal`
+
+Starts the internal MCP server which has more granular tools.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+- **Note:** This option is hidden from standard help output
+
+## Examples
+
+Start the standard MCP server:
+
+```
+but mcp
+```
+
+Start the internal MCP server with more granular tools:
+
+```
+but mcp --internal
+```
+
+
+# but-new.mdx
+
+## Usage
+
+```
+but new
+```
+
+## Arguments
+
+### ``
+
+Commit ID to insert before, or branch ID to insert at top of stack.
+
+- **Type:** String
+- **Required:** Yes
+
+## Examples
+
+Insert a blank commit before a specific commit:
+
+```
+but new abc123
+```
+
+Insert a blank commit at the top of a branch's stack:
+
+```
+but new my-feature-branch
+```
+
+
+# but-oplog.mdx
+
+## Usage
+
+```
+but oplog [OPTIONS]
+```
+
+## Description
+
+Displays the operation log (oplog) which tracks all operations performed in your GitButler workspace. This is useful for understanding what changes have been made and for potentially undoing or restoring to previous states.
+
+## Options
+
+### `--since `
+
+Start from this oplog SHA instead of the head.
+
+- **Type:** String
+- **Required:** Optional
+
+## Examples
+
+Show recent operation history:
+
+```
+but oplog
+```
+
+Show operation history from a specific point:
+
+```
+but oplog --since abc123def456
+```
+
+## Related Commands
+
+- [`but undo`](but-undo) - Undo the last operation
+- [`but restore`](but-restore) - Restore to a specific oplog snapshot
+- [`but snapshot`](but-snapshot) - Create an on-demand snapshot
+
+
+# but-publish.mdx
+
+## Usage
+
+```
+but publish [OPTIONS]
+```
+
+## Description
+
+By default, publishes reviews for all active branches in your workspace. You can optionally specify a single branch to publish.
+
+## Options
+
+### `-b, --branch `
+
+Publish reviews only for the specified branch.
+
+- **Type:** String
+- **Required:** Optional
+
+### `-f, --with-force`
+
+Force push even if it's not fast-forward.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+### `-s, --skip-force-push-protection`
+
+Skip force push protection checks.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-r, --run-hooks`
+
+Run pre-push hooks.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+## Examples
+
+Publish all active branches:
+
+```
+but publish
+```
+
+Publish a specific branch:
+
+```
+but publish --branch my-feature
+```
+
+Publish without running hooks:
+
+```
+but publish --run-hooks=false
+```
+
+
+# but-push.mdx
+
+## Usage
+
+```
+but push [OPTIONS]
+```
+
+## Arguments
+
+### ``
+
+Branch name or CLI ID to push.
+
+- **Type:** String
+- **Required:** Yes
+
+## Options
+
+### `-f, --with-force`
+
+Force push even if it's not fast-forward.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+### `-s, --skip-force-push-protection`
+
+Skip force push protection checks.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-r, --run-hooks`
+
+Run pre-push hooks.
+
+- **Type:** Flag (boolean)
+- **Default:** `true`
+
+## Gerrit Options
+
+The following options are only available when Gerrit mode is enabled for your repository:
+
+### `-w, --wip`
+
+Mark change as work-in-progress (Gerrit). Mutually exclusive with `--ready`.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-y, --ready`
+
+Mark change as ready for review (Gerrit). This is the default state. Mutually exclusive with `--wip`.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-a, --hashtag, --tag `
+
+Add hashtag(s) to change (Gerrit). Can be used multiple times.
+
+- **Type:** String (repeatable)
+- **Required:** Optional
+
+### `-t, --topic `
+
+Add custom topic to change (Gerrit). At most one topic can be set. Mutually exclusive with `--topic-from-branch`.
+
+- **Type:** String
+- **Required:** Optional
+
+### `--tb, --topic-from-branch`
+
+Use branch name as topic (Gerrit). Mutually exclusive with `--topic`.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+### `-p, --private`
+
+Mark change as private (Gerrit).
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Push a branch:
+
+```
+but push my-feature-branch
+```
+
+Force push without running hooks:
+
+```
+but push my-feature-branch --with-force --run-hooks=false
+```
+
+Push with Gerrit flags (when Gerrit mode is enabled):
+
+```
+but push my-feature-branch --ready --hashtag bug-fix --hashtag security
+```
+
+## Notes
+
+- Gerrit push flags (`--wip`, `--ready`, `--hashtag/--tag`, `--topic`, `--topic-from-branch`, `--private`) can only be used when gerrit_mode is enabled for the repository
+- `--wip` and `--ready` are mutually exclusive. Ready is the default state.
+- `--topic` and `--topic-from-branch` are mutually exclusive. At most one topic can be set.
+- Multiple hashtags can be specified by using `--hashtag` (or `--tag`) multiple times.
+
+
+# but-restore.mdx
+
+## Usage
+
+```
+but restore [OPTIONS]
+```
+
+## Arguments
+
+### ``
+
+Oplog SHA to restore to.
+
+- **Type:** String
+- **Required:** Yes
+
+## Options
+
+### `-f, --force`
+
+Skip confirmation prompt.
+
+- **Type:** Flag (boolean)
+- **Default:** `false`
+
+## Examples
+
+Restore to a specific snapshot (with confirmation):
+
+```
+but restore abc123def456
+```
+
+Restore without confirmation prompt:
+
+```
+but restore abc123def456 --force
+```
+
+## Related Commands
+
+- [`but oplog`](but-oplog) - Show operation history
+- [`but undo`](but-undo) - Undo the last operation
+- [`but snapshot`](but-snapshot) - Create an on-demand snapshot
+
+
+# but-rub.mdx
+
+## Usage
+
+```
+but rub
+```
+
+## Description
+
+The `rub` command is a versatile operation that combines two entities together to perform various operations depending on the types of entities provided.
+
+### Supported Operations
+
+| Source | Target | Operation |
+| ------ | ------ | --------- |
+| File | Commit | Amend |
+| Branch | Commit | Amend |
+| Commit | Commit | Squash |
+| File | Branch | Assign |
+| Branch | Branch | Assign |
+| Commit | Branch | Move |
+
+## Arguments
+
+### ``
+
+The source entity to combine.
+
+- **Type:** String
+- **Required:** Yes
+
+### ``
+
+The target entity to combine with the source.
+
+- **Type:** String
+- **Required:** Yes
+
+## Examples
+
+Amend a file to a commit:
+
+```
+but rub path/to/file.txt abc123
+```
+
+Squash two commits:
+
+```
+but rub abc123 def456
+```
+
+Assign a file to a branch:
+
+```
+but rub path/to/file.txt my-feature-branch
+```
+
+Move a commit to a different branch:
-
+```
+but rub abc123 my-other-branch
+```
-
-This means that the server has a public key that you used to sign the commits that is associated to your account and has verified that this user actually signed this commit.
+# but-snapshot.mdx
-In order for this to work, you need to:
+## Usage
-1. Tell GitButler to sign your commits
-2. Upload your key as a "signing key" to GitHub or GitLab (or elsewhere)
+```
+but snapshot [OPTIONS]
+```
-## Telling GitButler to Sign
+## Description
-For GitButler to sign commits, you need to setup Git to sign commits, as we do roughly the same thing that Git itself tries to do, and we read and respect most of the same Git config settings.
+Creates a snapshot of the current workspace state in the operation log. This is useful for creating manual checkpoints before making significant changes.
-The main difference is that instead of only the `commit.gpgSign` as the flag that tells Git to automatically sign commits, we look for `gitbutler.signCommits` first. Thus, if Git would sign, GitButler will attempt to sign your commits with the normal Git settings as well.
-But if something goes wrong, `gitbutler.signCommits` will be set to `false` in the repository-local settings to prevent commits from
-failing generally.
+## Options
-We look to see if we have a signing key in `user.signingkey`. If we have a key, we look for 'ssh' in `gpg.format`, otherwise we use GPG. We will respect `gpg.ssh.program` for ssh if there is a different binary path, and `gpg.program` for GPG. We also identify literal SSH keys in the `user.signingkey` field.
+### `-m, --message `
-The only major thing we don't support yet is `gpg.ssh.defaultKeyCommand` for other ways to get a key other than the `user.signingkey` field. We also don't support the X.509 smime stuff.
+Message to include with the snapshot.
-Here is an example, if you have a public key here `.ssh/id_ed25519.pub` then you can setup signing with something like this:
+- **Type:** String
+- **Required:** Optional
+
+## Examples
+
+Create a snapshot without a message:
-```bash title="Terminal"
-$ git config --global user.signingkey "/Users/schacon/.ssh/id_ed25519.pub"
-$ git config --global gpg.format ssh
-$ git config --global gitbutler.signCommits true
+```
+but snapshot
```
-You can also set this up in your project settings, perhaps a little more easily:
+Create a snapshot with a descriptive message:
-
+```
+but snapshot -m "Before refactoring authentication module"
+```
-The nice thing here is that you can also test the settings easily by hitting the "Test Signing" button.
+## Related Commands
-There are lots of other ways to set up GPG or SSH commit signing:
+- [`but oplog`](but-oplog) - Show operation history
+- [`but undo`](but-undo) - Undo the last operation
+- [`but restore`](but-restore) - Restore to a specific oplog snapshot
-- 1Password is a very easy way to [SSH sign commits](https://blog.1password.com/git-commit-signing/).
-- GitHub has a [good guide](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) on how to setup GPG or SSH keysigning.
-- Here is a nice simple gist for [GPG signing on Windows](https://gist.github.com/BoGnY/f9b1be6393234537c3e247f33e74094a).
-
-Earlier versions of GitButler would only sign with it's generated SSH key. Although we've removed that functionality, you can easily set it back up by pointing the signingKey at the generated SSH Key. The key is located in the following locations:
+# but-status.mdx
-
-
- ```bash
- /Users/[username]/Library/Application Support/com.gitbutler.app/keys/ed25519.pub
- ```
-
-
- ```bash
- C:\Users\[username]\AppData\Roaming\com.gitbutler.app\keys\ed25519.pub
- ```
-
-
- ```bash
- ~/.local/share/gitbutler-tauri/keys/ed25519.pub
- ```
-
-
+## Usage
-
+```
+but status [OPTIONS]
+```
-## Upload Your Signing Key
+## Alias
-For GitHub or GitLab to verify your signatures, you need to say that the SSH or GPG public key we are using is a valid signing key for your user.
+This command can also be invoked using the shorter alias `st`:
-### Adding to GitHub
+```
+but st [OPTIONS]
+```
-You can click on the "Add key to GitHub" link in the settings page right about the signing toggle, or you can go here (https://github.com/settings/ssh/new) to paste that public key in.
+## Example Output
-
+```cli but-st-c43eb45a
+but status
+```
-Now your signed commits should show up as "Verified".
+## Options
-### Adding to GitLab
+### `-f, --files`
-For GitLab you need to go to "SSH Keys" in your profile: https://gitlab.com/-/profile/keys and click the "Add new key" button.
+Determines whether the committed files should be shown as well.
-
+- **Type:** Flag (boolean)
+- **Default:** `false`
-Now paste in the public SSH key you copied from GitButler, name it and make sure the "Usage Type" is either "Signing" or "Authentication and Signing".
+### `-v, --verbose`
-
+Show verbose output with commit author and timestamp.
+- **Type:** Flag (boolean)
+- **Default:** `false`
-# upstream-integration.mdx
+### `-r, --review`
-Sometimes you work on a branch and someone else pushes to the same upstream branch. Often you won't know this until you try to push and Git tells you something like this:
+Show the forge review information.
-
+- **Type:** Flag (boolean)
+- **Default:** `false`
-In this scenario, GitButler gives you some nice tooling to help you know when this happens as early as possible and help you deal with it easily.
+## Examples
-If someone else has pushed to a branch that you're working on, you will see the upstream commits without having to integrate them. You can look at the commits without having to merge them into your branch or rebase your work on top of them.
+Show basic status:
-
+```
+but status
+```
-When you decide that you do want to integrate the changes, you have two options - rebase or interactively integrate.
+Show status with files:
-## Rebase the changes
+```
+but status --files
+```
-If you select "Rebase upstream changes", it will do the equivalent of a `git pull --rebase` which rebases the commits you have locally on top of the ones that the other person has pushed, so you end up with a state like this:
+Show verbose status with review information:
-
+```
+but status -v -r
+```
-Now you can push your commit back upstream without a force push. Easy peasy.
-## Interactively integrate the changes
+# but-undo.mdx
-However, let's say that you want to do something more complex. Maybe the other implemented the same thing that you did and you want to drop one of them or one of yours, or squash commits together or reorder them. In any of these cases, you can choose the "Interactive integration" option and you get something that looks like this:
+## Usage
+
+```
+but undo
+```
+
+## Description
+
+Reverts the workspace to the state it was in before the last operation. This uses the operation log (oplog) to roll back changes.
+
+## Options
+
+This command takes no specific options.
+
+## Examples
+
+Undo the last operation:
+
+```
+but undo
+```
+
+## Related Commands
+
+- [`but oplog`](but-oplog) - Show operation history
+- [`but restore`](but-restore) - Restore to a specific oplog snapshot
+- [`but snapshot`](but-snapshot) - Create an on-demand snapshot
+
+
+# but-unmark.mdx
+
+## Usage
+
+```
+but unmark
+```
+
+## Description
+
+This command removes all auto-assign and auto-commit rules (marks) from the workspace.
+
+## Options
+
+This command takes no specific options.
+
+## Examples
+
+Remove all marks:
+
+```
+but unmark
+```
+
+## Related Commands
+
+- [`but mark`](but-mark) - Creates or removes a rule for auto-assigning or auto-committing
+
+
+# installation.mdx
+
+How to install and setup the GitButler CLI.
+
+## Installing the `but` CLI
+
+Ok, first thing is first, let's get our `but` CLI installed. Currently there are two ways to do this.
+
+### Via the Desktop Client
+
+If you have the desktop client installed, you can go into your global settings and click on the "Install CLI" button in the "general" section.
-Here you can reorder commits however you want, you can choose to skip some of them, you can squash some of them down, etc. Just make the commits look however you prefer and then hit the "Integrate changes" button and push your final result back to the server.
+### Homebrew
+If you're running on a Mac and use Homebrew, you can install GitButler via `brew install gitbutler` and it will install the CLI for you automatically.
-# claude-code-hooks.mdx
+## Setup
-If you are using Claude Code, you can use the new ["hooks"](https://docs.anthropic.com/en/docs/claude-code/hooks) functionality to manage the output of even multiple simultaneous instances, while isolating all the generated code into virtual or stacked branches automatically. In this case, there is no need to set up the MCP server, as the hooks will handle everything for you.
+There isn't currently much setup you can do, but this will change in the near future, such as setting your name and email for commits or the editor to use for your commit messages.
-Here's a short video showing how GitButler works with Claude Code:
+The next step is to initialize an existing Git repository to use GitButler branching and tooling, which you can read about in the next section, the Tutorial.
-https://youtu.be/AwwPwSc9qhA
-Ok, let's get it set up.
+# branching-and-commiting.mdx
-## Install the GitButler CLI
+Now that your project is setup and GitButler is installed and configured, you can start branching and committing.
-First, you need to install the GitButler CLI, which can be done in your General settings. See the [MCP Server documentation](./mcp-server) for more details on how to install the CLI.
+## The Simple Flow
-## Installing GitButler as a Hook
+Let’s begin with a simple workflow, one that should be familiar to Git users. We will:
-Hooks in Claude Code are defined in one of your settings files.
+- Do some work
+- Create a new branch
+- Commit to that branch
-```
-~/.claude/settings.json - User settings
-.claude/settings.json - Project settings
-.claude/settings.local.json - Local project settings (not committed)
-```
+### Status
-Wherever you want to add GitButler to handle your commits automatically, you can add us as a hook by adding the following to the `hooks` array in whatever settings file you want to use:
+Let’s begin by seeing what the status of the working directory is by running `but status`. This will tell you a little more than `git status`, it will list:
-```json
-{
- "hooks": {
- "PreToolUse": [
- {
- "matcher": "Edit|MultiEdit|Write",
- "hooks": [
- {
- "type": "command",
- "command": "but claude pre-tool"
- }
- ]
- }
- ],
- "PostToolUse": [
- {
- "matcher": "Edit|MultiEdit|Write",
- "hooks": [
- {
- "type": "command",
- "command": "but claude post-tool"
- }
- ]
- }
- ],
- "Stop": [
- {
- "matcher": "",
- "hooks": [
- {
- "type": "command",
- "command": "but claude stop"
- }
- ]
- }
- ]
- }
-}
-```
+1. All files in your working directory that differ from your base branch (`origin/main`) the last time you updated it that aren’t assigned to a branch
+2. A list of the active branches that you have and
+ 1. All assigned file changes in each branch
+ 2. All commits in each branch
-Essentially, you want to run the `but claude pre-tool` command before any code generation or editing, and the `but claude post-tool` command after it. The `but claude stop` command will run when you stop the agent, ensuring that all changes are committed and branches are updated accordingly.
+So it's sort of like a combination of `git status` and a shortlog of what is on your branches that is not on `origin/master`.
-You also might want to add to your "memories" to ask Claude not to try commiting using Git as it will be handled by GitButler. You can do something like this:
+It looks something like this:
-```
-❯ cat ~/.claude/CLAUDE.md
-## Development Workflow
-- Never use the git commit command after a task is finished.
+{/* restore [f0f437258043] */}
+{/* run but rub wu gemfile-fixes */}
+{/* run but rub te feature-bookmarks */}
+
+```cli [7a684a53c92c90c9, 418px]
+but status
```
-## Using GitButler with Claude Code
+Here we can see three applied branches: `gemfile-fixes` stacked on `feature-bookmarks` and independent `sc-branch-26`. There are also three unassigned files.
-With the hooks setup, Claude will tell GitButler when it has generated code or edited files and in which session, which helps GitButler to try to isolate the changes into a single branch per session.
+### Create a Branch
-For example, if you have three sessions of Claude Code running at the same time, each will be communicating with GitButler at each step and GitButler will be assigning each change to the correct branch automatically.
+Let’s look at a very simple case first. Let’s say we’ve just modified some files and don’t have a branch yet. Our status might look like this:
-When the agent is done, GitButler will commit all the changes and write a more sophisticated commit message based on what you had prompted your agent.
+{/* restore [04d62f15beb4] */}
+```cli [a86fb55ce4c726bf, 220px]
+but status
+```
-# virtual-branches.mdx
+Now let’s say that we want to put those unassigned file changes into a commit on a new branch called `user-bookmarks`.
+To do this, you can use the `but branch new ` command.
-Virtual branches are a powerful feature of GitButler that allow you to work on multiple branches at the same time, committing to them independently and simultaneously. This is a key part of the GitButler experience, allowing you to manage your work in a flexible and efficient way that is not possible with traditional Git tooling.
+{/* run git branch -D user-bookmarks */}
-## Overview
+```cli [a976f522d9d44420, 44px]
+but branch new user-bookmarks
+```
-With normal Git branching, you can only work on one branch at a time. There is one `HEAD` reference and one index.
+Now if you run `but status` you can see your new empty branch:
+
+```cli [0af893c1f8c760bd, 286px]
+but status
+```
+
+### Commit to a Branch
+
+Now we can commit our unassigned changes to that branch. You can simply assign your changes to the branch first to commit later (we’ll cover that later in [Rubbing](https://www.notion.so/Rubbing-2545a4bfdeac80209d37cd4d629316cc?pvs=21)), but for now let’s keep it simple and just commit them directly using the `but commit` command.
+
+```cli [0110c7907c1cf514, 44px]
+but commit -m 'all the user bookmarks'
+```
-With virtual branches, you can have multiple branches applied to your working directory at the same time. Each branch is represented as a vertical lane, and you can drag changes between these lanes to commit them independently.
+If you don’t specify the `-m` commit message, GitButler will try to open an editor with a tempfile where you can write a longer commit message. It will use the `$EDITOR` environment variable if it’s set, or the `core.editor` Git or GitButler config setting, or it will prompt you for a command to run if you’re in an interactive terminal.
-Each lane also has it's own staging area, so you can stage changes for each branch before deciding to commit them.
+Now our status looks like this, with all unassigned files in a new commit on our new branch:
-
+```cli [6344d5831aee974f, 176px]
+but status
+```
-## How it works
+## Stacked and Parallel Branches
-Let's say that you make changes to two different files and `git status` would list two modified files. In GitButler, you can "assign" the change in each file to a different "virtual" branch, then when you commit, it will create a commit that only contains the changes in that file for that branch.
+Ok, that’s the simple case, pretty straightforward. However, GitButler can also do some pretty cool things that Git either cannot do or struggles with, namely having multiple active branches that you can work on in parallel, and managing stacked branches. That is, both multiple independent and dependent active branches.
-One of the nice things with this approach is that since you're starting from changes in a single working directory, you can be sure that all branches that you create from it will merge cleanly, as you're essentially starting from the merge product and extracting branches of work from it.
+### Parallel Branches
+Parallel branches is very simple, you can create multiple simultaneously active branches that you can assign and commit changes to in your workspace.
-# stacked-branches.mdx
+To create a parallel branch, you simply create a new branch the same way we did before. Let’s say that we want to create a `liked-tweets` branch alongside our existing `user-bookmarks`. We simply run the same `but branch new` command again:
+{/* run git branch -D liked-tweets */}
+{/* run echo 'test' > app/controllers/likes_controller.rb */}
+{/* run echo 'test' > app/models/like.rb */}
-Create a stack of dependent branches to be reviewed and merged in order.
+```cli [d3f1c48945fc9809, 44px]
+but branch new liked-tweets
+```
-## Overview
+Now if we run `but status` we can see our previous branch and our new empty branch.
-GitButler allows you to create an ordered stack of branches where each branch depends on (and is based on) the previous one.
-The application also supports creating the appropriate stacked Pull Requests (when used with a GitHub remote).
-This is useful when you have multiple changesets that depend on each other but it is desirable to have them reviewed and merged separately (and in sequence).
+```cli [64010dd6e8529339, 286px]
+but status
+```
-> All of the Pull Request stack orchestration is done locally in the client, which means that your repo content is not shared with a cloud service.
+We can see our previous branch and the commit we made, our new empty branch and a couple of modified files. Now we can commit the unassigned changes to that branch with `but commit -m "liked tweets changes" liked-tweets`
-
+```cli [706747fbcb1889b7, 44px]
+but commit -m "liked tweets changes" liked-tweets
+```
-## Use cases
+And now we have one commit in each lane.
-Using stacked branches (Pull Requests) can be helpful for shipping smaller changes more frequently.
+```cli [3987f8d371e20e27, 264px]
+but status
+```
-### Breaking up a larger change into smaller ones
+Here we specified the entire branch name as the commit target (as there is more than one), but you can also use the two character short code that is next to each one.
-Consider a scenario where you are implementing a medium/large feature in your software project.
-In the course of implementation you end up performing the following sub-tasks:
+If you don’t specify a branch identifier and you have more than one active branch, then GitButler will prompt you for which branch you wish to commit the unassigned changes to.
-1. Refactor a part of the codebase to accommodate the new feature
-2. Implement an API endpoint supporting the feature
-3. Implement the frontend part of the feature consuming the API
+We can also see which files were modified in each commit with the `--files` or `-f` option to `but status`:
-While the feature is considered complete only when all of the subtasks are implemented, reviewed and merged, in many cases it is considered beneficial
-to ship each stage of the feature on its own, potentially behind a feature flag. Not only the risk of merge conflicts with colleagues is reduced,
-but also eventual bugs are easier to track down / revert / fix as compared to a single large change.
+```cli [c679f47525fe3aa6, 440px]
+but status -f
+```
-### More granular (easier) review process
+### Stacked Branches
-On GitHub at least, code reviews are performed on per-branch basis. While it is possible to view individual commits in a Pull Request, it is not possible to
-approve and merge a subset of commits from the PR.
+The other way you can create new branches is to make them stacked, that is, one depends on another one and has to be merged in that order.
-Utilizing stacked pull requests, means that the sub-tasks of a larger change are in their own PRs.
-This way it is possible to approve and merge the initial part of a stack (e.g. a refactor) while still iterating on the remaining sub-tasks.
+To create a new stacked branch in GitButler, you can run `but branch new` with a target branch ID. If we go back in time and instead stack our `liked-tweets` branch, we can make it dependent on the `user-bookmarks` branch by providing it as a stacking "anchor" with `-a` option:
-## Comparison to Virtual Branches
+{/* run git branch -D liked-tweets-stacked */}
+{/* restore [e32713a1f41c] */}
-Stacking and Virtual Branches are similar in that they allow you to separate code changes / commits into different branches. In both cases,
-the changes are available in your working directory.
+```cli [f78665426ff0c23f, 44px]
+but branch new -a user-bookmarks liked-tweets-stacked
+```
-The main difference is that Virtual Branches are **independent** from one another, while stacked branches **depend** on the ones that come before it.
-Because of this, the two features are not mutually exclusive but rather complementary. For example a bugfix change that is unrelated to a feature
-can be put in a separate virtual branch. On the other hand, a change that depends on a previous change can be put in a stacked branch above the one it depends on.
+```cli [5fb2d0bcb941c804, 286px]
+but status
+```
-In fact GitButler implements stacked branches as Virtual Branches that are split into multiple dependent branches.
+Now we can commit to our stacked branch.
-
+```cli [10aecd5f47e264a7, 44px]
+but commit -m "liked tweets changes" liked-tweets-stacked
+```
-## Workflow
+```cli [c9fb470a128dcff6, 242px]
+but status
+```
-By default, virtual branches in the app are simply stacks of one.
-With version `0.14.0` or newer you can create a new dependent branch within a lane by clicking the `+` button above the branch name.
+Now if you push to a forge, GitButler will set up the reviews (Pull Request or Merge Request) as a stacked request, where `user-bookmarks` has to be merged either before or with `liked-tweets` but they can be reviewed independently.
-> The workflow below assumes a GitHub remote. If you are using a different forge, you can still use this functionality but will need to manually create/update the Pull/Merge Requests
+## Assigning and Committing Changes
-1. Creating a new dependent branch forms a stack within the lane.
+The other way to commit to a branch is to explicitly assign changes to it. This is somewhat like running `git add` in Git, where you’re staging some changes for a future commit. However, unlike Git where you have to do this or override it with `-a` or something, the default in GitButler is to commit all changes by default and only leave out unassigned changes with the flag `-o` or `--only`.
-
+### Assigning Changes
-2. New commits land in the top branch of the stack.
+So, how do we assign changes to a specific branch and then only commit those changes?
-
+Let’s look at an example `but status` with six modified files and two empty, parallel branches and assign and commit one file to each branch as a separate commit.
-3. Pushing is done for the stack as a whole. Note: The Pull Requests will be created in a way where each branch points to its parent - see [Automatic branch deletion](#automatic-branch-deletion)
+{/* restore [d5c7317b0fd4] */}
-
+```cli [b0a042fa1f10b0a8, 352px]
+but status
+```
-4. Pull requests must be created one at a time starting from the bottom of the stack.
+We will assign each file to a different branch and then see the result. We assign file changes to branches using the `but rub` command, which combines things. We'll go more into all that rubbing can do later.
-
+You can either rub the file identifier that you see next to each file, or all or part of the file path. For example, in this case to identify the `app/models/bookmark.rb` file, you can do any of:
-5. The PRs will contain a footer with stack information, and as you add more PRs it will keep all up to date.
+- `xw`
+- `app/models/`
+- `bookmark`
+- `app/models/bookmark.rb`
-
+In order to differentiate a shortcode from a path, a shortcode is exactly 2 characters and a path needs to be at least 3. This is the same pattern matching used for the branch ID too.
-6. You can drag changes into commits to amend them (e.g. incorporating review feedback) as well as move and squash commits.
+So lets rub the bookmark changes into the bookmarks branch:
-
-
-
+```cli [2a8b35a5f3fdc773, 88px]
+but rub xw,ie,rt user-bookmarks
+```
-7. If a change in your stack is independent (e.g. an unrelated bugfix) it can be moved to a different virtual branch (or stack).
- This works for both uncommitted changes and existing commits that you may want to relocate.
+Now let's rub the user changes into the `user-changes` branch:
-
+```cli [41851621cd9071e1, 44px]
+but rub ku user-changes
+```
-8. Review/merge your PRs starting from the bottom up. After a PR/branch from your stack has been merged, it is reflected in the Stack and you should force push to reflect the changes
- on the remote as well.
+Now we have some file changes assigned to each branch and still some unassigned changes:
-
-
+```cli [734c068c0c406e82, 352px]
+but status
+```
-9. When all branches of a stack have been merged, the stack is complete.
+Now, if we want to create a commit in the `user-bookmarks` branch, we can either run `but commit nd` which will create a commit with the files assigned as well as both files that are unassigned, but _not_ the file assigned to the `user-changes` lane.
-## GitHub configuration for stacked PRs
+Or, we can make a commit with _only_ the assigned files in `user-bookmarks` by using the `-o` option to `but commit`.
-_TLDR:_
+```cli [21fa219c49d8f8fe, 44px]
+but commit -o -m "liked tweets view" h4
+```
-1. Enable automatic branch deletion [automatic branch deletion](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-the-automatic-deletion-of-branches)
- on GitHub.
-2. If possible, consider using the the "Merge" strategy when merging PRs.
+Now if we look at our status we can see a commit on our branch instead of the assigned changes:
-#### Automatic branch deletion
+```cli [8aec10380a37afc0, 352px]
+but status
+```
-When reviewing a PR in a stack, it is important to be able to view only the changes in the branch that is being reviewed.
-Of course, in pure Git terms, a stacked branch will contain all the changes from the branches below it.
+Now let's commit all the rest of the changes (assigned and unassigned) to our other branch:
-In order to show only the expected Files changed and Commits for PRs in a stack, each PR is created to target the branch below it in the stack.
-This is true for all but the bottom branch in the stack, which targets the default branch of the repository as usual.
+```cli [f20a0ef692d37799, 44px]
+but commit -m 'bookmarks stuff' nd
+```
-
+```cli [1cb6d694f8882426, 264px]
+but status
+```
-> Every branch in the stack contains the commits from the branches below it.
+### Assigning Ranges
-This of course does not mean that a Pull Request should be merged into its parent.
-When the bottom branch is merged on GitHub, **if** the PR branch is deleted,
-GitHub will automatically update any PRs that used to target it to target the default branch instead.
+If you happen to have a large number of changes, you can also use ranges or lists for rubbing assignment. So for example, if we go back to this status:
-
+{/* restore [6fdd8fb1d547] */}
+{/* run but rub nd 00 */}
-If the newly merged branch from the bottom of the stack is not deleted, the next PR in line will still target it and there is a risk of accidentally merging it into the now out of date branch.
-For this reason it is _highly recommended_ to [enable on GitHub](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-the-automatic-deletion-of-branches) the automatic deletion of branches after merging.
+```cli [b0a042fa1f10b0a8, 352px]
+but status
+```
-_NB:_ If you merge the first PR but the branch is not deleted and then merge the second PR,
-the app can still recover from this, see [Troubleshooting](#troubleshooting).
+Then you can assign the first, third, and fifth file to a branch with:
-#### Merge strategy
+```cli [bf6d997a5b35a132, 88px]
+but rub nx,xw,rt user-bookmarks
+```
-The app will support any merge strategy you wish to use - "Merge", "Rebase" or "Squash". However, due to the nature of merge,
-the GitButler will be able to create a slightly better experience if the "Merge" strategy is used.
-The reason for this is with merge commits you will be able to merge all the branches in the stack from GitHub without having to force push in the app.
+```cli [0729d431d2061013, 352px]
+but status
+```
-## Troubleshooting
-Firstly, if you run into any issue with the app (stacking or not), you can always get in touch either
-on [Discord](https://discord.com/invite/MmFkmaJ42D) or via the in-app feedback icon (we will get back via email).
-With that said, here are some workarounds for common issues.
+# editing-commits.mdx
-#### Accidentally merged a stack branch into an already merged branch before it
+While you can rub changes in and out of commits, you can also edit the commit
+message of any commit in your workspace quite easily.
-If you merged the bottom Pull Request into main but the branch was _not_ deleted, then the target of the next Pull Request would not be automatically updated.
-Under these conditions merging that next Pull Request, means it would be merged into the original, now out of date, bottom PR.
+## Editing Commit Messages
-A mitigation for this is to rename the branch, push and re-create the Pull Request.
+You can edit commit messages with the `but describe` command. So if we have this status:
-#### Accidentally merged a branch into a branch before it (not integrated into main/master yet)
+{/* restore [d69fffa7c6eb] */}
-Merging of branches in a stack should be done from the bottom up. With the GitHub interface, it is possible to incorrectly merge a Pull Request which is in the middle of the stack.
-In this case it will merged in the parent branch.
+```cli [387e58cb9b4bbbce, 198px]
+but status
+```
-In order to recover from this situation you can simply force push the branches and then re-create the PR that was incorrectly merged.
+Then you can edit the message of any commit by running `but describe `, which will open up your editor of choice with the existing commit message and when you exit the editor, replace that message in the commit and rebase everything above it.
+The editor would look something like this:
-# ai-overview.mdx
+```
+add user changes
+
+# Please enter the commit message for your changes. Lines starting
+# with '#' will be ignored, and an empty message aborts the commit.
+#
+# Changes in this commit:
+# modified: app/models/user.rb
+# modified: config/routes.rb
+#
+~
+~
+~
+~
+```
+Pretty simple.
-If you're using AI agent tools like Cursor, Windsurf, or Claude Code, GitButler can enhance your coding experience by managing commits, saving points, and more. These integrations allow you to focus on coding with your agents while GitButler handles the version control aspects.
+{/* TODO: Edit Mode */}
-https://www.youtube.com/watch?v=J6xV_Wyz9zg
-There are currently two main ways to integrate AI tools with GitButler:
+# initializing-a-repository.mdx
-1. **Using the MCP Server**: This method allows you to set up your AI agent to communicate with GitButler's MCP server, enabling features like automatic commits and save points.
-2. **Using the Hooks in Claude Code**: This method allows you to use GitButler's hooks to manage commits and branches directly from within the Claude Code environment.
+If you run any `but` command in a repository that has never been seen by GitButler before, it will automatically run the `but init` command, which will need to have you confirm the upstream base branch (`origin/main` for example).
-## Enabling the experimental feature flag
+You can also run `but init` manually to set that up first, or
+```
+❯ but init
+Initialized GitButler project from .
+The default target is origin/main
+```
-Note that as of GitButler version `0.15.2` these features have to be enabled via an experimental feature flag. You can find that under `Global Settings` -> `Experimental` -> `GitButler Actions`.
+If there is an `origin/HEAD` reference (servers will often set this as a target branch), GitButler will use this.
+Otherwise, the `but init` command will try to guess a default base branch by looking for `origin/main` or `origin/master`, as these are very common. If it finds one of these, it will set it and move on. If it does not, it will ask you which remote branch it should use as it’s target base.
-# mcp-server.mdx
+A base branch has to be set on every project GitButler operates on. This is important to tell what is or is not considered "integrated".
+
-If you use an AI agent (such as Cursor, Windsurf, Claude Code) to help you with your code, you can easily setup GitButler to manage your commits automatically, keep save points, and more. You know, _vibe_ commit...
+Currently a base branch needs to be on a remote. Our workflow currently assumes you are pushing to a forge and merging after review on that branch to the target base. Support for local-only workflows is coming, but not current possible.
-## Setting up your Agent to use GitButler
+
-The first step is to let your agent know about GitButler, which is done via MCP - you need to tell your agent to use the GitButler MCP server.
-### Installing the CLI
+# operations-log.mdx
-GitButler provides a CLI that can be used to interact with the GitButler platform. Before you can setup AI Agent integration, you will need to install the CLI.
+GitButler maintains a detailed log of all operations, making it easy to track what happened and undo changes when needed.
-This can be found by opening the GitButler global settings, and then clicking on the "Install CLI" button in the General settings.
+## Viewing the Operations Log
-
+See all recent GitButler operations:
-Now that you have the `but` CLI installed, your agent can use the CLI's MCP server to interact with GitButler.
+```cli [e9d6ef69b35f2728, 506px]
+but oplog
+```
-### Cursor
+## Undoing the last operation
-To install the GitButler MCP server in Cursor, first go to the Cursor settings, and then click on the "Extensions" tab, then click on "Tools and Integrations" and click on "New MCP Server".
+Undo the last operation:
-This will open your `~/.cursor/mcp.json` file.
+```cli [3019b8bf7d04ba45, 88px]
+but undo
+```
-Add the following to the `mcpServers` object:
+## Restoring to a previous point
-```json
-{
- "mcpServers": {
- "gitbutler": {
- "command": "but",
- "args": ["mcp"]
- }
- }
-}
+```cli [bacd8a0236b5ed4a, 176px]
+but restore -f 6fdd8fb1d547
```
-You should see the GitButler MCP server in the list of MCP servers and it should have the tool `gitbutler_update_branches` available.
-### VSCode
+# rubbing.mdx
-To install the GitButler MCP server in VSCode, you need to select "MCP: List Servers" from the actions menu. Then select "Add Server". Select "stdio" as the server type.
+# Rubbing
-Now you can type your command (`but mcp`) and name it something. After this, it should open up your settings file and show you something like this:
+As we saw in the [Branching and Committing](branching-and-commiting) section, the `but rub` command can be used to assign changes to branch lanes.
-```json
- "mcp": {
- "servers": {
- Running | Stop | Restart | 1 tools
- "gitbutler-mcp": {
- "type": "stdio",
- "command": "but",
- "args": ["mcp"]
- }
- }
- }
-```
+However, it can be used to do _so much_ more. Rubbing is essentially combining two things. Since there are lots of _things_ in the tool, combining them together can do lots of different operations. Most of them should be fairly intuitive once you understand the concept.
-However, if you have Cursor's MCP already setup, VSCode will notice and help you automatically reuse the settings.
+Let’s take a look at what is possible with this very straightforward command.
-
+# Unassigning Changes
-### Claude Code
+We already showed how you can use `rub` to assign a file change or set of changes to a branch for later committing (rubbing a file and a branch), but what if you want to undo that? Move assignments to a different lane or revert them to being unassigned for later?
-Adding an MCP server to Claude Code is done by running the `claude mcp add` command.
+As you may have noticed in the `but status` output, there is a special identifier `00` which is always the “unassigned” ID. If you rub anything to `00` then it will move it to unassigned.
-```
-❯ claude mcp add gitbutler but mcp
-Added stdio MCP server gitbutler with command: but mcp to local config
+So given this status:
-❯ claude mcp list
-gitbutler: but mcp
+```cli [7a684a53c92c90c9, 418px]
+but status
```
-## Rules: How to configure auto committing
+We can re-unassign the `README.new.md` file with `but rub np 00`. Or, we can re-assign that file to the `sc-branch-26` parallel branch with `but rub np q6`.
-Once you have installed the MCP server in your editor or agent, you can optionally configure it to automatically commit your changes.
+# Amending Commits
-We've found that adding something like this to your rules works well:
+However, assignment is not all we can do with rubbing. We can also use it to move things to and from commits. A common example would be to amend a commit with new work.
-```
-If you generate code or modify files, run the gitbutler update branches MCP tool.
+Let’s say that we sent commits out for review and got feedback and instead of creating new commits to address the review, we wanted to actually fix up our commits to be better. This is somewhat complicated to do in Git (something something [fixup commit, autosquash](https://blog.gitbutler.com/git-autosquash), etc). However, with `rub` it’s incredibly simple. Just rub the new changes into the target commit rather than a branch.
+
+Let’s say that we have a branch with some commits in it, we’ve made changes to two files and want to amend two different commits with the new changes.
+
+```cli [7a684a53c92c90c9, 418px]
+but status
```
-## How to add rules
+If we want to update the first commit (`5d70b6f`) with the `like_button.rb` changes and the last commit (`9ed9612`) with the `liked_tweets.html.erb` changes, we can run the following two `rub` commands:
-Cursor stores it's rules in `~/.cursor/rules` file, but you can also manually set them by going to the Cursor Settings pane, clicking 'Rules' and adding them to the User Rules section.
+[rub command]
-In VSCode's Copilot Agent Mode, you can use ["custom instructions"](https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions) to accomplish this.
+Notice that the SHAs have changed, but the commit identifiers have not. It has rewritten the commits to have the same messages but incorporated the changes you rubbed into those patches.
-In Claude Code, they are now called "memories" and you can add them by hitting '#' and storing them in user memory (or local if you just want them in one project).
+If you wanted to rub all the unassigned changes into a specific commit, you could also do that by rubbing the unassigned section to a commit, for example `but rub 00 1l` which would take all unassigned changes and amend commit `1l` with them.
-
+# Squashing Commits
-Or directly in your `~/.claude/CLAUDE.md` rules file:
+File changes are not the only thing that you can rub. You can also rub commits into things. To squash two commits together, you simply rub them together. Let’s take the last status output and squash the first commit into the third:
```
-❯ cat ~/.claude/CLAUDE.md
-## Development Workflow
-- When you're done with a task where code was created or files edited, please run the gitbutler mcp update_branches command.
+❯ but rub ux 1l
+
+Squashed commits:
+ - 8df823d ("Show recent active users on admin page")
+into:
+ - d78fe05 ("Add liked tweets section to the LikesController")
```
-## Using GitButler with your agent
+Now we can see that we only have two commits in our branch:
-If you've set up a rule/instruction/memory, then every time a chat session is completed, the agent will send the changes and prompt to GitButler and it will automatically commit the changes.
+```
+❯ but status
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+****
+╭ **8x** **[sc-liked-tweets]**
+● **9r** 7a4ecf4 Add Liked Tweets page to dashboard
+● **1l** 7de8f04 Add liked tweets section to the LikesController
+╯
+
+**00** **Unassigned Changes**
+ (none)
+```
-
+If we look at `1l` now, it has combined the commit messages and associated changes into a single commit. It’s possible that this will introduce a merge conflict to the commit that was in the middle (_which won’t happen if you squash two neighboring commits, but in this case we used a slightly more complex skip-squash_), but you can always [undo it](https://www.notion.so/Operations-Log-2545a4bfdeac8087a518dcf7448f62d3?pvs=21) or [resolve the conflict](https://www.notion.so/Resolving-Conflicts-2545a4bfdeac80b7a587c2b9f23ca890?pvs=21).
-If you're using Claude Code, it may look something like this:
+You probably want to [edit the commit message](https://www.notion.so/Editing-Commits-2545a4bfdeac809b985ec774d1beb32f?pvs=21) after this too, since it will simply combine the two commit messages.
-
+# Uncommitting
-If you don't have the agent setup to automatically call our tool, then you can also just manually type 'update gitbutler branches' in the chat, but that's a little less magical.
+Let’s say that we want to just _undo_ a commit - that is, pretend that we had not made that commit and instead put the changes back to unassigned status. In this case we would use the special `00` ID that we talked about earlier, just like unassigning changes, we can unassign commits.
-## GitButler interface
+So, if we’re back to this status:
-There are two phases to GitButler's MCP agent interaction. The first is the agent sending the changes and prompt to GitButler, which GitButler will quickly record and then return a success to the agent. The second is GitButler processing that raw recorded change and attempting to process that change into a commit.
+```
+❯ but status
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+****
+╭ **8x** **[sc-liked-tweets]**
+● **ux** 8df823d Show recent active users on admin page
+● **9r** 937ace4 Add Liked Tweets page to dashboard
+● **1l** d78fe05 Add liked tweets section to the LikesController
+╯
+
+**00** **Unassigned Changes**
+ (none)
+```
-### Recording the changes
+And we want to un-commit the first commit (`1l`) as though we had never made it, you can rub to `00`:
-When your agent calls the `gitbutler_update_branches` tool, GitButler will record the changes and prompt and then immediately return to the agent, so the call should be very fast.
+```
+❯ but rub 1l 00
-So for instance, let's say that I prompted my coding agent to update my `README.md` file to add a list of contributing authors. When the agent is done, it should call the update branches MCP tool, which will record a commit that looks something like this:
+Uncommitted d78fe05 ("Add liked tweets section to the LikesController")
-
+Newly unassigned changes:
+**gt** M app/views/dashboard/liked_tweets.html.erb
+**6i** M app/components/tweet/like_button.rb
+```
-### Processing the changes
+Now if we look at our status again, we will see that commit removed and those files back in the unassigned status:
-Then, if you have AI tooling setup, GitButler will see that and turn it into a commit message like this:
+```
+❯ but status
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+****
+╭ **8x** **[sc-liked-tweets]**
+● **ux** 9ed9612 Show recent active users on admin page
+● **9r** 1970ca4 Add Liked Tweets page to dashboard
+╯
+
+**00** **Unassigned Changes
+gt** M app/views/dashboard/liked_tweets.html.erb
+**6i** M app/components/tweet/like_button.rb
+```
-
+# Moving Commits
-You can see all of these steps in the "Actions" section of the GitButler interface, which you can toggle by hitting the "Actions" button in the top right of the interface.
+We can also use rubbing to move a commit from one branch to another branch if we have multiple active branches and committed to the wrong one, or otherwise decide that we want to split up independent work.
-
+Let’s say that we have two commits on one branch and one commit on a second parallel branch and want to move one:
-In the near future, we will also be able to do more interesting things like auto-absorbing changes into existing commits, creating new branches based on the prompt theme, creating stacked branches, and more.
+```
+❯ but status
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+
+****╭ **p3** **[liked-tweets]**
+● **2p** 2acf832 liked tweets view
+╯
+╭ **8x** **[cache-tester]**
+● **e8** f8f3e3a test tweeting functionality
+● **q9** a9cfef2 add cache tests
+╯
+
+**00** **Unassigned Changes**
+ (none)
+```
+We can move the “tweeting tests” commit to the `liked-tweets` branch with `but rub`:
-# debugging.mdx
+```
+❯ but rub e8 p3
+Moved commit f8f3e3a ("test tweeting functionality...")
+ to: branch **liked-tweets**
+```
-If you are having technical issues with the GitButler client, here are a few things you can do to help us help you. Or help yourself.
+Now we can see that the commit has been moved to the top of the new branch:
-If you get stuck or need help with anything, hit us up over on Discord, here's [GitButler Discord Server Link](https://discord.gg/MmFkmaJ42D).
+```
+❯ but status
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+
+****╭ **p3** **[liked-tweets]**
+● **e8** 8efdea6 test tweeting functionality
+● **2p** 2acf832 liked tweets view
+╯
+╭ **8x** **[cache-tester]**
+● **q9** a9cfef2 add cache tests
+╯
+
+**00** **Unassigned Changes**
+ (none)
+```
-
+Notice that the only SHA that changed was the one that moved, since nothing else needed to be rebased. Rubbing a commit to another branch always adds it to the top of that branch. If you then want to move it lower in the branch, you can use the [`but move`](https://www.notion.so/move-2065a4bfdeac802a88cff5498b409ec2?pvs=21) command.
-The first things to try is checking out the frontend related logs in the console by opening the developer tools in GitButler via the "View" -> "Developer Tools" menu option. Next, if you launch GitButler from the command line, you can view the backend logs directly in your terminal.
+As you might imagine, you can also simultaneously move and squash by rubbing a commit in one branch on a commit in another branch too.
-## Logs
+# Moving Files and Hunks between Commits
-Often the most helpful thing is to look at the logs. GitButler is a Tauri app, so the logs are in your OS's [app log directory](https://docs.rs/tauri/1.8.1/tauri/api/path/fn.app_log_dir.html). This should be:
+You can also move files, hunks, or lines from one commit to another, or from unassigned to a commit, much like you can move stuff from unassigned to a lane via “[Partial Assigning](https://www.notion.so/Branching-and-Committing-2545a4bfdeac80c5a48ffb6cf219181c?pvs=21)” (ranges and whatnot).
-
-
- ```bash
- ~/Library/Logs/com.gitbutler.app/
- ```
-
-
- ```bash
- C:\Users\[username]\AppData\Local\com.gitbutler.app\logs
- ```
-
-
- ```bash
- ~/.config/gitbutler/logs/ [OR]
- ~/.local/share/gitbutler-tauri/logs/
- ```
-
-
+To do that, you need identifiers for the files and hunks in an existing commit, which you can get via a `but status -f`, or `but status --files` that tells status to also list commit file IDs.
-In this directory, there should be rolling daily logs:
+```
+❯ but status -f
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+
+****╭ **p3** **[liked-tweets]**
+● **e8** 8efdea6 **test tweeting functionality**
+│ **6i** M app/components/tweet/like_button.rb
+│ **gt** M app/views/dashboard/liked_tweets.html.erb
+│
+● **2p** 2acf832 **liked tweets view**
+│ **i4** M app/controllers/tweet_controller.rb
+╯
+╭ **8x** **[cache-tester]**
+● **q9** a9cfef2 **add cache tests**
+│ **6q** A tests/scripts/cache_tester.t
+│ **dx** A scripts/debug/cache_tester.sh
+╯
+
+**00** **Unassigned Changes**
+ (none)
+```
+So now we can move the changes from one commit to another by rubbing pretty easily. Let’s take the `liked_tweets.html.erb` change and move it down to the “liked tweets view” commit:
-```bash title="Terminal"
-❯ cd ~/Library/Logs/com.gitbutler.app
+```
+❯ but rub gt 2p
-❯ tree -L 1
+Moved to commit 2acf832 ("liked tweets view..."):
+ - M app/views/dashboard/liked_tweets.html.erb
+```
-├── GitButler.log
-├── GitButler.log.2023-09-02
-├── GitButler.log.2023-09-03
-├── GitButler.log.2023-09-04
-├── GitButler.log.2023-09-05
-├── GitButler.log.2023-09-06
-├── GitButler.log.2023-09-07
-├── GitButler.log.2023-09-08
-├── GitButler.log.2023-10-10
-├── GitButler.log.2024-01-30
-└── tokio-console
+Now the change is in the previous commit:
-❯ tail GitButler.log.2024-01-30
-2024-01-30T13:02:56.319843Z INFO get_public_key: gitbutler-app/src/keys/commands.rs:20: new
-2024-01-30T13:02:56.320000Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: new key="gitbutler.utmostDiscretion"
-2024-01-30T13:02:56.320117Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: new key="gitbutler.signCommits"
-2024-01-30T13:02:56.320194Z INFO get_public_key: gitbutler-app/src/keys/commands.rs:20: close time.busy=317µs time.idle=47.0µs
-2024-01-30T13:02:56.320224Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: close time.busy=204µs time.idle=25.3µs key="gitbutler.utmostDiscretion"
-2024-01-30T13:02:56.320276Z INFO git_get_global_config: gitbutler-app/src/commands.rs:116: close time.busy=133µs time.idle=35.8µs key="gitbutler.signCommits"
-2024-01-30T13:02:56.343467Z INFO menu_item_set_enabled: gitbutler-app/src/menu.rs:11: new menu_item_id="project/settings" enabled=false
-2024-01-30T13:02:56.343524Z INFO menu_item_set_enabled: gitbutler-app/src/menu.rs:11: close time.busy=35.7µs time.idle=28.8µs menu_item_id="project/settings" enabled=false
+```
+❯ but status -f
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+
+****╭ **p3** **[liked-tweets]**
+● **e8** ed3fde4 **test tweeting functionality**
+│ **6i** M app/components/tweet/like_button.rb
+│
+● **2p** c4def87 **liked tweets view**
+│ **i4** M app/controllers/tweet_controller.rb
+│ **gt** M app/views/dashboard/liked_tweets.html.erb
+╯
+╭ **8x** **[cache-tester]**
+● **q9** a9cfef2 **add cache tests**
+│ **6q** A tests/scripts/cache_tester.t
+│ **dx** A scripts/debug/cache_tester.sh
+╯
+
+**00** **Unassigned Changes**
+ (none)
```
+Even hunks within files are movable, the same way that we saw in the “[Partial Assigning](https://www.notion.so/Branching-and-Committing-2545a4bfdeac80c5a48ffb6cf219181c?pvs=21)” section:
-## Data Files
+```
+❯ but rub gt:3-5 2p
-GitButler also keeps it's own data about each of your projects. The virtual branch metadata, your user config stuff, a log of changes in each file, etc. If you want to inspect what GitButler is doing or debug or reset everything, you can go to our data directory.
+Moved to commit 2acf832 ("liked tweets view..."):
+ - M app/views/dashboard/liked_tweets.html.erb (3-5)
+```
-
-
- ```bash
- ~/Library/Application Support/com.gitbutler.app/
- ```
-
-
- ```bash
- C:\Users\[username]\AppData\Roaming\com.gitbutler.app
- ```
-
-
- ```bash
- ~/.local/share/gitbutler-tauri/
- ```
-
-
+Now parts of the change to that file are in two different commits:
-In this folder there are a bunch of interesting things.
+```
+❯ but status -f
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+
+****╭ **p3** **[liked-tweets]**
+● **e8** ed3fde4 **test tweeting functionality**
+│ **6i** M app/components/tweet/like_button.rb
+│ **gt** M app/views/dashboard/liked_tweets.html.erb (1-2, 6-9)
+│
+● **2p** c4def87 **liked tweets view**
+│ **i4** M app/controllers/tweet_controller.rb
+│ **gt** M app/views/dashboard/liked_tweets.html.erb (3-5)
+╯
+╭ **8x** **[cache-tester]**
+● **q9** a9cfef2 **add cache tests**
+│ **6q** A tests/scripts/cache_tester.t
+│ **dx** A scripts/debug/cache_tester.sh
+╯
+
+**00** **Unassigned Changes**
+ (none)
+```
+# Splitting Commits
-```bash title="Terminal"
-❯ cd ~/Library/Application\ Support/com.gitbutler.app
+Ok, so now we can be pretty specifc about moving changes around to all these different states. The last thing we’ll cover here is splitting commits, which requires a new command that creates a new empty commit called `but new`.
-❯ tree
-.
-├── keys
-│ ├── ed25519
-│ └── ed25519.pub
-├── projects.json
-└── settings.json
+By default, `but new` will create a new empty commit at the top of the most recently created branch, however you can specify a different branch, or even a specific place within a branch with existing commits.
-4 directories, 4 files
+Let’s say that we’re back to this state:
+
+```
+❯ but status
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+
+****╭ **p3** **[liked-tweets]**
+● **e8** 8efdea6 test tweeting functionality
+● **2p** 2acf832 liked tweets view
+╯
+╭ **8x** **[cache-tester]**
+● **q9** a9cfef2 add cache tests
+╯
+
+**00** **Unassigned Changes**
+ (none)
```
+Now we want to split the “liked tweets view” back into two separate commits. The way we do this is to insert a blank commit in between `e8` and `2p` and then rub changes into it (then probably edit the commit message).
-The `projects.json` file will have a list of your projects metadata:
+We can insert a blank commit by running `but new p3` which inserts a blank commit at the top of that branch. Or, we could also say `but new e8`, specifying the first commit on that branch and it will insert one above that. Or we could say `but new 2p` which would insert a blank in between the two commits on that branch. Or, we could insert one at the base of the branch by putting one below `2p` with `but new -b 2p` (short for `--below`). Let’s do the simple version of just adding it to the top of the branch:
+```
+❯ but new p3
-```bash title="Terminal"
-❯ cat projects.json
-[
- {
- "id": "71218b1b-ee2e-4e0f-8393-54f467cd665b",
- "title": "gitbutler-blog",
- "description": null,
- "path": "/Users/scottchacon/projects/gitbutler-blog",
- "preferred_key": "generated",
- "ok_with_force_push": true,
- "api": null,
- "gitbutler_data_last_fetch": null,
- "gitbutler_code_push_state": null,
- "project_data_last_fetch": {
- "fetched": {
- "timestamp": {
- "secs_since_epoch": 1706619724,
- "nanos_since_epoch": 202467000
- }
- }
- }
- }
-]
+Created new blank commit above:
+ - 8efdea6 test tweeting functionality
```
+Now we have a blank commit:
-The `settings.json` are some top level preferences you've set.
+```
+❯ but status
+
+📍 Base: origin/main @ 83afc2a
+🔺 You are 32 commits behind origin/main
+ (Run `but base update` to rebase your stack)
+
+****╭ **p3** **[liked-tweets]**
+● **6w** f4f96e3 (blank)
+****● **e8** 8efdea6 test tweeting functionality
+● **2p** 2acf832 liked tweets view
+╯
+╭ **8x** **[cache-tester]**
+● **q9** a9cfef2 add cache tests
+╯
+
+**00** **Unassigned Changes**
+ (none)
+```
+Now we can use the previous method of moving file changes from other commits into it, then edit the commit message with `but describe 6w` (for more on the `describe` command, see [Editing Commits](https://www.notion.so/Editing-Commits-2545a4bfdeac809b985ec774d1beb32f?pvs=21), coming up next).
-```bash title="Terminal"
-❯ cat settings.json
-{
- "appAnalyticsConfirmed": true,
- "appNonAnonMetricsEnabled": true
-}
+
+# scripting.mdx
+
+## JSON Mode
+
+```cli [57c76ba368268a45, 16522px]
+but -j status
```
-Finally, the `keys` directory holds the SSH key that we generate for you in case you don't want to go through creating your own. It's only used if you want to use it to sign commits or use it for authentication.
+# tutorial-overview.mdx
-## Linux
+https://www.youtube.com/watch?v=loavN_hHuEs
-### `glibc` Errors
+Using the GitButler CLI is meant to make a specific common workflow very simple, which is roughly:
-The Linux installation is currently being built in a GitHub Action with Ubuntu 24.04. This means support is limited to those installations using the same or newer version of `glibc`. Unfortunately we cannot build using earlier versions of Ubuntu due to another incompatibility with `libwebkit2gtk-4.1` and Tauri at the moment.
+- Create a branch
+- Do work on that branch
+- Commit to that branch
+- Optionally, create another branch if you find unrelated work you need to do
+- Work on and commit to that branch
+- Submit a branch for review
+- Create a stacked branch if needed to continue on dependent work
+- Update your base if work has been integrated to remove merged work
+- Rinse and repeat
-If you're using an older distribution, you may be interested in trying our Flatpak package available on Flathub.
+Additionally, GitButler is very good at editing commits (amending fixup work, squashing, rewording messages, etc), it keeps a simple log of what you've done in case you need to go back in time, it makes collaborating on a branch with others easy, it has great GitHub/Lab integration and more.
-### `Failed to create EGL image from DMABuf`
+Let's walk through some of the things it can do and what a typical day using the GitButler CLI might look like.
-If you start GitButler from the command line and see a bunch of these or similar `EGL` / `DMABuf` related messages printed to the console and are only getting a white screen to render, you can try launching GitButler with the following environment variables:
-- `WEBKIT_DISABLE_DMABUF_RENDERER=1`
-- `WEBKIT_DISABLE_COMPOSITING_MODE=1`
+# updating-the-base.mdx
-This issue most likely stems from an incompatibility between your version of OpenGL (`mesa`) and `libwebkit2gtk-4.1`.
+The base branch is the foundation that your feature branches build upon. Keeping it updated and managing it properly is crucial for a smooth workflow.
+## Understanding the Base Branch
-# contact-us.mdx
+The base branch is typically your main development branch (`main`, `master`, or `develop`). All your feature branches are conceptually built on top of this base.
+## Viewing Upstream
-There are a few ways to get in touch with us for feedback, bug reports, feature requests, etc.
+What is upstream
-
- }
- href="mailto:hello@gitbutler.com"
- title="Email"
- description="The simplest way to get in touch with us is to email us"
- />
- }
- href="https://discord.com/invite/MmFkmaJ42D"
- title="Discord"
- description="We are also available to chat on our Discord server"
- />
-
+## Updating the Base from Remote
+
+Keep your base branch updated with the remote repository:
+
+## Changing the Base Branch
+
+You can change which branch serves as your base:
# open-source.mdx
@@ -2629,39 +4501,53 @@ As part of our commitment to open source, we are an early member of the [Open So
You can read more about our reasoning to join the Open Source Pledge in our announcement blog post and 2024 report: [GitButler Joins the Open Source Pledge](https://blog.gitbutler.com/open-source-pledge-2024).
-# supporters.mdx
+# integration-branch.mdx
-Thinking about paying for Beta software? Sounds odd, right?
+Bundling all your virtual branches together
-No worries, the main stuff in GitButler stays the same whether you pay or not.
+Since GitButler does some pretty fun stuff with branches in order to enable virtual branches to work, some Git commands run from other git clients, including stock Git in the terminal are bound to behave a little strangely.
-But hey, we're all about building a cool gang here. We want to know who really digs our butler. And those early supporters? They're like VIPs to us.
+We're getting the git data to think in three dimensions, then asking 2-D Git how to deal with it.
-## Perks for Early Supporters
+While some commands cannot work well because of this single-branch limitation (commit, checkout), we do try our best to keep everything in a state where most other commands work reasonably well. Anything having to do with the index or HEAD is a little problematic, but doable in a smooshed state (all branches look like one), while commands like `log` or `blame` work totally fine with no shenanigans.
-- Access to our Early Bird Discord room, for life
-- Invitations to exclusive Berlin parties, when it's warm here
-- Care packages of schwag, sent your way
-- Pricing locked in, no matter how we decide to charge later
-- First look at any new features as we go
-- Whatever else we can think of over time
+## The Integration Commit
+The way that we handle this relatively well is by creating an "integration" commit every time you change the committed state of your collective virtual branches.
-Your support helps us grow and make GitButler even better. Join us on this adventure!
+
-## How to Support Us
-You need to have a GitButler account to support us. If you don't have one, sign up first.
-
- }
- href="https://app.gitbutler.com/supporter"
- title="GitButler"
- description="Support GitButler with a monthly contribution"
- />
-
+So what is an "integration" commit? Well, when you apply or unapply branches, or you commit on one of your applied branches, you change the state of what GitButler sees as your overall committed state with regards to your working directory.
-Thanks, from the GitButler Crew!
+## Status, Diff and Log
-
+To keep Git command output for things that look at the index and HEAD (such as `status` or `diff`) somewhat sane, we modify your index to look like the union of all the committed states of all your applied virtual branches. This makes `git diff` and `git status` behave more or less like you would expect.
+
+For instance, if you have two files on Branch A and two files on Branch B, then `git status` will simply list four files as modified.
+
+However, to help out, we also write out a branch with a custom commit message that tries to explain the state of things and what is happening. This is written to a branch we own called `gitbutler/workspace` and you shouldn't touch it.
+
+If you run `git log`, the first commit should be our custom commit message and the tree of that commit is the union of all the committed work on all your applied virtual branches, as though they were all merged together into one (something stock Git can understand).
+
+## Committing, Branching, Checking Out
+
+However, if you try to use something that writes to HEAD, like `git commit` or `git checkout`, then you might have some headaches. By default, our client will simply overwrite the `gitbutler/workspace` branch commit whenever something significant changes.
+
+We won't touch the working directory in an unexpected way, so whatever you commit won't be lost, but the commit itself will be forgotten. Don't do branch stuff in stock Git while trying to use GitButler for now. We have ideas on how to make this somewhat doable in the future, but right now it's easier on everyone to stick with one or the other.
+
+## Git Add and the Index
+
+If you attempt to modify the index directly (running `git add` or `git rm`), GitButler won't notice or care. It will simply overwrite it with whatever it needs during its operations, so while I wouldn't do it, there is also not a lot of danger.
+
+The worst that would happen is that you do some complex `git add -i` patch staging and then we wipe it out by rewriting the index. But again, you shouldn't be using stock Git commands related to committing or branching. You gotta choose one or the other for now, you can't go back and forth super easily.
+
+## Recovering or Stopping GitButler Usage
+
+If you want to stop using GitButler and go back to using stock Git commands for committing and branching, simply check out another branch. GitButler will realize that you've changed its branch and stop functioning until you reset it.
+To help with remembering where you were, the integration commit should have the branch name and commit SHA that you were on when GitButler was initially activated. You should be able to easily go back to that branch and its last known commit state.
diff --git a/scripts/sync-commands.sh b/scripts/sync-commands.sh
new file mode 100755
index 0000000..13c7e3a
--- /dev/null
+++ b/scripts/sync-commands.sh
@@ -0,0 +1,140 @@
+#!/bin/bash
+
+# Script to sync command documentation from CLI repository
+# Usage: ./scripts/sync-commands.sh [source-repo-path]
+# Default source repo: ../gitbutler/cli-docs
+
+set -e
+
+# Get the directory where the script is located
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# Get the project root (parent of scripts directory)
+PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+# Change to project root to ensure relative paths work correctly
+cd "$PROJECT_ROOT"
+
+# Configuration
+SOURCE_REPO="${1:-../gitbutler/cli-docs}"
+TARGET_DIR="content/docs/commands"
+CLI_EXAMPLES_DIR="public/cli-examples"
+GROUPS_FILE="$TARGET_DIR/groups.json"
+META_FILE="$TARGET_DIR/meta.json"
+
+echo "Syncing command documentation..."
+echo "Source: $SOURCE_REPO"
+echo "Target: $TARGET_DIR"
+echo
+
+# Check if source directory exists
+if [ ! -d "$SOURCE_REPO" ]; then
+ echo "Error: Source directory '$SOURCE_REPO' does not exist"
+ exit 1
+fi
+
+# Check if groups.json exists
+if [ ! -f "$GROUPS_FILE" ]; then
+ echo "Error: groups.json not found at $GROUPS_FILE"
+ exit 1
+fi
+
+# Remove existing MDX files (but keep groups.json and meta.json)
+echo "Removing existing MDX files..."
+rm -f "$TARGET_DIR"/*.mdx
+
+# Copy MDX files from source
+echo "Copying MDX files from source repository..."
+if ! cp "$SOURCE_REPO"/*.mdx "$TARGET_DIR/" 2>/dev/null; then
+ echo "Warning: No MDX files found in source directory"
+fi
+
+# Count copied files
+COPIED_FILES=$(ls -1 "$TARGET_DIR"/*.mdx 2>/dev/null | wc -l | tr -d ' ')
+echo "Copied $COPIED_FILES MDX files"
+
+# Sync cli-examples directory
+echo
+echo "Syncing cli-examples directory..."
+
+# Check if source cli-examples directory exists
+SOURCE_CLI_EXAMPLES="$SOURCE_REPO/cli-examples"
+if [ -d "$SOURCE_CLI_EXAMPLES" ]; then
+ # Create target cli-examples directory if it doesn't exist
+ mkdir -p "$CLI_EXAMPLES_DIR"
+
+ # Remove existing HTML files from target
+ echo "Removing existing CLI example files..."
+ rm -f "$CLI_EXAMPLES_DIR"/*.html
+
+ # Copy HTML files from source
+ echo "Copying CLI example files..."
+ if cp "$SOURCE_CLI_EXAMPLES"/*.html "$CLI_EXAMPLES_DIR/" 2>/dev/null; then
+ COPIED_EXAMPLES=$(ls -1 "$CLI_EXAMPLES_DIR"/*.html 2>/dev/null | wc -l | tr -d ' ')
+ echo "Copied $COPIED_EXAMPLES CLI example files"
+ else
+ echo "Warning: No HTML files found in $SOURCE_CLI_EXAMPLES"
+ fi
+else
+ echo "Warning: cli-examples directory not found at $SOURCE_CLI_EXAMPLES"
+fi
+
+# Generate meta.json using Node.js
+echo "Generating meta.json..."
+node -e "
+const fs = require('fs');
+const path = require('path');
+
+// Read groups.json
+const groupsPath = path.join('$TARGET_DIR', 'groups.json');
+const groups = JSON.parse(fs.readFileSync(groupsPath, 'utf8'));
+
+// Get all MDX files
+const files = fs.readdirSync('$TARGET_DIR')
+ .filter(f => f.endsWith('.mdx'))
+ .map(f => f.replace('.mdx', ''));
+
+// Track which commands are assigned to groups
+const assignedCommands = new Set();
+Object.values(groups).forEach(commands => {
+ commands.forEach(cmd => assignedCommands.add(cmd));
+});
+
+// Find unassigned commands
+const unassignedCommands = files.filter(f => !assignedCommands.has(f)).sort();
+
+// Build pages array
+const pages = [];
+
+// Add grouped commands
+Object.entries(groups).forEach(([groupName, commands]) => {
+ // Only add groups that have commands present in the files
+ const presentCommands = commands.filter(cmd => files.includes(cmd));
+ if (presentCommands.length > 0) {
+ pages.push(\`---\${groupName}---\`);
+ presentCommands.forEach(cmd => pages.push(cmd));
+ }
+});
+
+// Add miscellaneous section if there are unassigned commands
+if (unassignedCommands.length > 0) {
+ pages.push('---Miscellaneous---');
+ unassignedCommands.forEach(cmd => pages.push(cmd));
+}
+
+// Create meta.json structure
+const meta = {
+ title: 'Commands',
+ defaultOpen: false,
+ pages: pages
+};
+
+// Write meta.json
+const metaPath = path.join('$TARGET_DIR', 'meta.json');
+fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + '\n');
+
+console.log('Generated meta.json with ' + pages.filter(p => !p.startsWith('---')).length + ' commands');
+"
+
+echo
+echo "Sync completed successfully!"
+echo "You can now view the updated commands in $TARGET_DIR"
diff --git a/scripts/update-cli-outputs.js b/scripts/update-cli-outputs.js
new file mode 100644
index 0000000..b017b1c
--- /dev/null
+++ b/scripts/update-cli-outputs.js
@@ -0,0 +1,429 @@
+#!/usr/bin/env node
+
+import fs from 'fs/promises';
+import path from 'path';
+import { execSync } from 'child_process';
+import crypto from 'crypto';
+import { glob } from 'glob';
+
+class CliOutputUpdater {
+ constructor() {
+ this.config = null;
+ this.hasChanges = false;
+ this.changedFiles = [];
+ this.obsoletedHashes = new Set();
+ this.usedHashes = new Set();
+ }
+
+ async loadConfig() {
+ try {
+ const configContent = await fs.readFile('cli-examples-run.json', 'utf8');
+ this.config = JSON.parse(configContent);
+ } catch (error) {
+ throw new Error(`Failed to load cli-examples-run.json: ${error.message}`);
+ }
+ }
+
+ async ensureCacheDir() {
+ const cacheDir = this.config.config.cacheDir;
+ try {
+ await fs.access(cacheDir);
+ } catch {
+ await fs.mkdir(cacheDir, { recursive: true });
+ }
+ }
+
+ async cleanupObsoletedCacheFiles() {
+ // Remove hashes that are still being used from the obsoleted set
+ for (const usedHash of this.usedHashes) {
+ this.obsoletedHashes.delete(usedHash);
+ }
+
+ // Delete remaining obsoleted cache files
+ for (const obsoletedHash of this.obsoletedHashes) {
+ const cacheFile = path.join(this.config.config.cacheDir, `${obsoletedHash}.html`);
+ try {
+ await fs.unlink(cacheFile);
+ console.log(`Removed obsoleted cache file: ${cacheFile}`);
+ } catch (error) {
+ // File might not exist, which is fine
+ console.log(`Obsoleted cache file not found (already cleaned): ${cacheFile}`);
+ }
+ }
+
+ if (this.obsoletedHashes.size > 0) {
+ console.log(`Cleaned up ${this.obsoletedHashes.size} obsoleted cache files`);
+ }
+ }
+
+ async initializeProject() {
+ const { exampleProjectPath, startingHash } = this.config;
+
+ // Store the original docs directory
+ const docsDirectory = process.cwd();
+
+ console.log(`Changing to example project: ${exampleProjectPath}`);
+ process.chdir(exampleProjectPath);
+
+ console.log(`Restoring to starting hash: ${startingHash}`);
+ try {
+ const restoreCommand = this.replaceButCommand(`but restore --force ${startingHash}`);
+ execSync(restoreCommand, { stdio: 'inherit' });
+ } catch (error) {
+ console.warn(`Warning: Failed to restore to starting hash ${startingHash}: ${error.message}`);
+ }
+
+ // Return to docs directory
+ console.log(`Returning to docs directory: ${docsDirectory}`);
+ process.chdir(docsDirectory);
+ }
+
+ hashContent(content) {
+ return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
+ }
+
+ replaceButCommand(command) {
+ if (!this.config.but_path) {
+ return command;
+ }
+
+ // Replace 'but ' at the beginning of the command
+ if (command.startsWith('but ')) {
+ return command.replace(/^but /, `${this.config.but_path} `);
+ }
+
+ // Replace standalone 'but' command
+ if (command === 'but') {
+ return this.config.but_path;
+ }
+
+ return command;
+ }
+
+ async runCommand(command, workingDir) {
+ const originalDir = process.cwd();
+ try {
+ if (workingDir) {
+ process.chdir(workingDir);
+ }
+
+ const output = execSync(command, {
+ encoding: 'utf8',
+ stdio: ['inherit', 'pipe', 'pipe'],
+ env: {
+ ...process.env,
+ COLOR_OVERRIDE: 'true'
+ }
+ });
+
+ return output;
+ } catch (error) {
+ // Return error output for display
+ return error.stdout || error.stderr || `Command failed: ${command}`;
+ } finally {
+ process.chdir(originalDir);
+ }
+ }
+
+ async convertToHtml(ansiOutput, hash, outputPath, command, workingDir) {
+ // Use ansi-senor CLI to convert ANSI output to HTML
+ // ansi-senor runs the command itself and captures output
+ const ansiSenorPath = this.config.ansi_senor_path || 'ansi-senor';
+ const fullCommand = `"${ansiSenorPath}" -o "${outputPath}" -t light ${command}`;
+
+ console.log(`Running ansi-senor command: ${fullCommand}`);
+ console.log(`Working directory: ${workingDir}`);
+
+ try {
+ const result = execSync(fullCommand, {
+ cwd: workingDir,
+ encoding: 'utf8',
+ stdio: ['pipe', 'pipe', 'pipe'],
+ env: {
+ ...process.env,
+ CLICOLOR_FORCE: '1',
+ }
+ });
+
+ console.log(`ansi-senor output: ${result}`);
+ // HTML file is written by ansi-senor
+ } catch (error) {
+ console.error(`ansi-senor command: "${ansiSenorPath}" -o "${outputPath}" -t light ${command}`);
+ console.error(`ansi-senor stderr: ${error.stderr}`);
+ console.error(`ansi-senor stdout: ${error.stdout}`);
+ console.warn(`Failed to run ansi-senor: ${error.message}`);
+ // Fallback: run command directly and create simple HTML
+ try {
+ const output = await this.runCommand(command, workingDir);
+ const plainText = output.replace(/\x1b\[[0-9;]*m/g, '');
+ const fallbackHtml = `
+
+
+
+
+
+
+
${plainText}
+
+`;
+ await fs.writeFile(outputPath, fallbackHtml);
+ } catch (fallbackError) {
+ console.error(`Failed to create fallback HTML: ${fallbackError.message}`);
+ throw error; // Re-throw original error
+ }
+ }
+ }
+
+ async processMdxFile(filePath) {
+ //console.log(`Processing: ${filePath}`);
+
+ const content = await fs.readFile(filePath, 'utf8');
+ const lines = content.split('\n');
+ let modified = false;
+ let currentRestore = null;
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+
+ // Check for restore commands
+ const restoreMatch = line.match(/^{\/\* restore \[([^\]]+)\] \*\/}$/);
+ if (restoreMatch) {
+ currentRestore = restoreMatch[1];
+ console.log(`Found restore command: ${currentRestore}`);
+ continue;
+ }
+
+ // Check for run commands
+ const runMatch = line.match(/^{\/\* run (.+) \*\/}$/);
+ if (runMatch) {
+ const runCommand = runMatch[1].trim();
+ console.log(`Found run command: ${runCommand}`);
+
+ // Execute the command in the example project directory
+ try {
+ const output = await this.runCommand(runCommand, this.config.exampleProjectPath);
+ console.log(`Run command output:\n${output}`);
+ } catch (error) {
+ console.warn(`Failed to execute run command "${runCommand}": ${error.message}`);
+ }
+ continue;
+ }
+
+ // Check for cli blocks
+ const cliBlockStart = line.match(/^```cli(?:\s+\[([^\]]+)\])?$/);
+ if (cliBlockStart) {
+ const existingParams = cliBlockStart[1];
+ let existingHash, existingHeight;
+
+ if (existingParams) {
+ const params = existingParams.split(',').map(p => p.trim());
+ existingHash = params[0] || undefined;
+ existingHeight = params[1] || undefined;
+ }
+
+ // Find the command and closing block
+ let j = i + 1;
+ let command = '';
+ while (j < lines.length && !lines[j].startsWith('```')) {
+ if (command) command += '\n';
+ command += lines[j];
+ j++;
+ }
+
+ if (j >= lines.length) {
+ console.warn(`Unclosed cli block in ${filePath} at line ${i + 1}`);
+ continue;
+ }
+
+ command = command.trim();
+ if (!command) {
+ console.warn(`Empty cli block in ${filePath} at line ${i + 1}`);
+ continue;
+ }
+
+ console.log(`Found CLI command: ${command}`);
+
+ // Run restore command if needed
+ if (currentRestore) {
+ console.log(`Running restore: but restore ${currentRestore}`);
+ const restoreCommand = this.replaceButCommand(`but restore --force ${currentRestore}`);
+ await this.runCommand(restoreCommand, this.config.exampleProjectPath);
+ currentRestore = null; // Reset after use
+ }
+
+ // Execute the command via ansi-senor (replace but path if needed)
+ const actualCommand = this.replaceButCommand(command);
+
+ // Generate HTML to a temporary file first
+ const tmpHash = crypto.randomBytes(8).toString('hex');
+ // Use absolute path so ansi-senor writes to the correct location
+ const tmpHtmlPath = path.resolve(this.config.config.cacheDir, `tmp-${tmpHash}.html`);
+
+ try {
+ await this.convertToHtml(null, tmpHash, tmpHtmlPath, actualCommand, this.config.exampleProjectPath);
+
+ // Check if the file was created
+ try {
+ await fs.access(tmpHtmlPath);
+ } catch (error) {
+ throw new Error(`ansi-senor did not create output file: ${tmpHtmlPath}`);
+ }
+
+ // Read the generated HTML and hash it
+ const htmlContent = await fs.readFile(tmpHtmlPath, 'utf8');
+ const hash = this.hashContent(htmlContent);
+ console.log(`Generated hash for command "${command}" in ${filePath}: ${hash}`);
+
+ // Calculate height based on line count in the body content (22px per line)
+ const bodyMatch = htmlContent.match(/]*>([\s\S]*)<\/body>/i);
+ const bodyContent = bodyMatch ? bodyMatch[1] : htmlContent;
+ const lineCount = bodyContent.split('\n').length;
+ const calculatedHeight = `${lineCount * 22}px`;
+ console.log(`Calculated height: ${calculatedHeight} (${lineCount} lines)`);
+
+ // Track all hashes being used in this run
+ this.usedHashes.add(hash);
+
+ // Check if hash or height changed
+ const hashChanged = existingHash && existingHash !== hash;
+ const heightChanged = existingHeight !== calculatedHeight;
+ const isNewBlock = !existingHash;
+
+ if (hashChanged) {
+ console.log(`Hash changed for command "${command}" in ${filePath}`);
+ console.log(` Old hash: ${existingHash}`);
+ console.log(` New hash: ${hash}`);
+
+ // Mark old hash as obsoleted instead of immediately deleting
+ this.obsoletedHashes.add(existingHash);
+
+ this.hasChanges = true;
+ this.changedFiles.push({
+ file: filePath,
+ command,
+ oldHash: existingHash,
+ newHash: hash,
+ height: calculatedHeight,
+ heightChanged
+ });
+ } else if (heightChanged && existingHash) {
+ console.log(`Height changed for command "${command}" in ${filePath}`);
+ console.log(` Old height: ${existingHeight || 'none'}`);
+ console.log(` New height: ${calculatedHeight}`);
+
+ this.hasChanges = true;
+ this.changedFiles.push({
+ file: filePath,
+ command,
+ oldHash: existingHash,
+ newHash: hash,
+ height: calculatedHeight,
+ heightChanged: true
+ });
+ } else if (isNewBlock) {
+ console.log(`New CLI block found: ${command}`);
+ this.hasChanges = true;
+ this.changedFiles.push({
+ file: filePath,
+ command,
+ oldHash: null,
+ newHash: hash,
+ height: calculatedHeight
+ });
+ }
+
+ // Rename the temporary file to the final hash-based name
+ const htmlPath = path.resolve(this.config.config.cacheDir, `${hash}.html`);
+ await fs.rename(tmpHtmlPath, htmlPath);
+
+ // Update the MDX file line with hash and height
+ if (hashChanged || heightChanged || isNewBlock) {
+ lines[i] = `\`\`\`cli [${hash}, ${calculatedHeight}]`;
+ modified = true;
+ }
+ } catch (error) {
+ // Clean up temporary file on error
+ try {
+ await fs.unlink(tmpHtmlPath);
+ } catch {}
+ throw error;
+ }
+
+ // Skip to after the closing ```
+ i = j;
+ }
+ }
+
+ if (modified) {
+ await fs.writeFile(filePath, lines.join('\n'));
+ console.log(`Updated: ${filePath}`);
+ }
+ }
+
+ async findMdxFiles() {
+ return await glob('content/**/*.mdx');
+ }
+
+ async run() {
+ console.log('Starting CLI output update process...');
+
+ await this.loadConfig();
+ await this.ensureCacheDir();
+ await this.initializeProject();
+
+ const mdxFiles = await this.findMdxFiles();
+ console.log(`Found ${mdxFiles.length} MDX files`);
+
+ for (const file of mdxFiles) {
+ await this.processMdxFile(file);
+ }
+
+ if (this.hasChanges) {
+ console.log('\n=== CHANGES DETECTED ===');
+ for (const change of this.changedFiles) {
+ console.log(`File: ${change.file}`);
+ console.log(`Command: ${change.command}`);
+ if (change.oldHash) {
+ console.log(`Hash changed: ${change.oldHash} → ${change.newHash}`);
+ } else {
+ console.log(`New hash: ${change.newHash}`);
+ }
+ if (change.height) {
+ console.log(`Height: ${change.height}`);
+ }
+ if (change.heightChanged) {
+ console.log(`Height was updated automatically`);
+ }
+ console.log('---');
+ }
+ } else {
+ console.log('No changes detected.');
+ }
+
+ // Clean up obsoleted cache files at the end
+ console.log('\n=== CLEANING UP OBSOLETED CACHE FILES ===');
+ await this.cleanupObsoletedCacheFiles();
+
+ console.log('CLI output update process completed.');
+ }
+}
+
+// Run the updater
+const updater = new CliOutputUpdater();
+updater.run().catch(error => {
+ console.error('Error:', error.message);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/source.config.ts b/source.config.ts
index 16dd6d1..f1d7c4b 100644
--- a/source.config.ts
+++ b/source.config.ts
@@ -1,6 +1,7 @@
import { defineConfig, defineDocs } from "fumadocs-mdx/config"
import remarkYoutube from "remark-youtube"
import { remarkMermaid } from "@/components/mermaid"
+import { remarkCli } from "@/app/components/remark-cli"
import { remarkHeading, remarkImage, remarkStructure, rehypeCode } from "fumadocs-core/mdx-plugins"
export const { docs, meta } = defineDocs({
@@ -17,7 +18,7 @@ export default defineConfig({
dark: "catppuccin-mocha"
}
},
- remarkPlugins: [remarkHeading, remarkImage, remarkStructure, remarkYoutube, remarkMermaid],
+ remarkPlugins: [remarkHeading, remarkImage, remarkStructure, remarkYoutube, remarkMermaid, remarkCli],
rehypePlugins: (v) => [rehypeCode, ...v]
}
})
diff --git a/tailwind.config.js b/tailwind.config.js
index 00b2127..c848aec 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -10,5 +10,22 @@ export default {
"./node_modules/fumadocs-ui/dist/**/*.js",
"./node_modules/fumadocs-openapi/dist/**/*.js"
],
+ theme: {
+ extend: {
+ fontFamily: {
+ mono: [
+ 'JetBrainsMono Nerd Font',
+ 'JetBrains Mono',
+ 'SF Mono',
+ 'Monaco',
+ 'Inconsolata',
+ 'Fira Code',
+ 'Fira Mono',
+ 'Roboto Mono',
+ 'monospace'
+ ]
+ }
+ }
+ },
presets: [createPreset()]
}