backstep 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/cli.js +1 -1
- package/dist/keys.js +23 -2
- package/dist/setup.js +3 -15
- package/dist/undo.js +1 -1
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/hooks/register.test.tsx +11 -11
- package/mod/hooks/register.tsx +8 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ Undo for everything your agents touch. Backstep sits between your agent (Claude
|
|
|
8
8
|
- **Supabase:** `execute_sql` and `apply_migration` snapshot the tables they change, over a connection to the project's database. Backstep never connects it on its own: when Claude Code opens a project that uses Supabase and isn't connected, it asks (Connect, Not now, or Don't ask for this project). Connect uses a connection string in the project's `.env` files, the local `supabase start` database, or a Supabase sign-in (`supabase login`, `SUPABASE_ACCESS_TOKEN`, or an access token you give Backstep once), with which the project gets a temporary database login that Backstep renews as it runs out. You can also ask Claude to "connect Backstep to Supabase" any time. Deleted rows, dropped tables and migrations, including the migration's history row, all come back. Works for the claude.ai Supabase connector and a local Supabase MCP server alike.
|
|
9
9
|
- **Firebase:** before a change, Backstep reads what's there through the Firebase MCP server itself, and undoes through it too, so there's nothing to set up. Firestore documents updated, deleted or added; Realtime Database writes; Remote Config publishes (rolled back to the version before); and users enabled or disabled.
|
|
10
10
|
- **claude.ai connectors and servers Claude Code signs in to:** Backstep can't sit in front of these, so the Claude Code mod watches their calls instead. Calls with an undo rule are undone through Claude Code's own connection.
|
|
11
|
-
- **Database commands Claude runs** (`supabase db push`, `supabase migration up`, `supabase db reset`, `psql`, `prisma migrate`, `drizzle-kit push`, seed scripts…): in a project whose Supabase database is connected, the whole database is noted before the command runs, and ctrl+
|
|
12
|
-
- **Code Claude pushed:** ctrl+
|
|
11
|
+
- **Database commands Claude runs** (`supabase db push`, `supabase migration up`, `supabase db reset`, `psql`, `prisma migrate`, `drizzle-kit push`, seed scripts…): in a project whose Supabase database is connected, the whole database is noted before the command runs, and ctrl+u puts it back in one transaction: rows, new tables dropped, dropped or altered tables rebuilt with their constraints, indexes, foreign keys, row level security policies, triggers and grants, sequence counters, and the migration history. Copies sit in a `backstep` schema in the same database; the newest 10 per database are kept. On a very big database it keeps every table's structure and the migration history, and rows smallest-table-first up to a budget (`BACKSTEP_DB_SNAPSHOT_MB`, default 250); undo names any table whose rows it couldn't bring back. Functions, views, types and extensions aren't restored.
|
|
12
|
+
- **Code Claude pushed:** ctrl+u rewinds the commits locally, then asks whether to undo them on GitHub (or any remote) too. Yes pushes one new commit that undoes Claude's, made in a temporary worktree: no force push, no rewritten history, so it works on protected branches and for teammates who already pulled. Newer commits that clash, or a rewritten branch, stop it. A branch Claude created is deleted instead, if nobody has added to it. It uses the project's own git sign-in, so there's nothing to connect.
|
|
13
13
|
- **Things that can't be undone** (sending email, payments, deploys, `ALTER TABLE`…) run as normal and are marked in the journal, so you know what an undo didn't cover.
|
|
14
14
|
- **Anything else, by rule:** a rules file maps a tool to its opposite (`create_issue` → close the issue, `send_message` → retract it), filled in from the call and its result. See [Undo rules](#undo-rules).
|
|
15
15
|
- **Conflicts:** undoing an action first undoes any later action that touched the same file or table, so nothing gets silently thrown away.
|
|
@@ -20,7 +20,7 @@ Undo for everything your agents touch. Backstep sits between your agent (Claude
|
|
|
20
20
|
npx backstep setup
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This installs Backstep, adds its Claude Code mod, binds **ctrl+
|
|
23
|
+
This installs Backstep, adds its Claude Code mod, binds **ctrl+u** to undo everything Claude did in its last turn, and puts the MCP servers you've already added to Claude Code behind Backstep, so their changes can be undone too. If you use Supabase, it asks for your database's connection string (Enter skips; you can ask Claude to connect it later). Restart Claude Code afterwards. Needs Node 22.13+ and Claude Code.
|
|
24
24
|
|
|
25
25
|
Servers in a repo's shared `.mcp.json` that you've approved get a wrapped copy for that project only; the repo's file is never edited. Remote servers that Claude Code signs in to stay as they are, and the mod watches their calls instead. Servers you add later are wrapped the next time Claude Code starts. `backstep unwrap` puts every server back the way it was and stops new ones being wrapped.
|
|
26
26
|
|
|
@@ -53,7 +53,7 @@ With Claude Code: `claude mcp add files -- backstep wrap --name files -- <the se
|
|
|
53
53
|
| `--rules <file>` | undo rules (or set `BACKSTEP_RULES`) |
|
|
54
54
|
| `--pg <url>` | the Postgres database the wrapped server writes to. Enables database undo. Without it, SQL writes are recorded but can't be undone. |
|
|
55
55
|
|
|
56
|
-
The undo key is ctrl+z. The first press asks "Undo Claude's last turn?" above the prompt and a second press within 5 seconds undoes, so a stray press does nothing. Change the key whenever you like: ask Claude ("change my Backstep undo key to ctrl+
|
|
56
|
+
The undo key is ctrl+u (not ctrl+z: Claude Code suspends itself on ctrl+z in a terminal before anything else sees the key, so it can't be used). The first press asks "Undo Claude's last turn?" above the prompt and a second press within 5 seconds undoes, so a stray press does nothing. Change the key whenever you like: ask Claude ("change my Backstep undo key to ctrl+q"), or run `backstep key ctrl+q`. The line above the prompt is always there, even with nothing to undo, because the key only reaches Backstep while it's on screen; otherwise a press would do nothing.
|
|
57
57
|
|
|
58
58
|
Backstep checks npm for a newer version as each Claude Code session starts and asks before updating (Update, Not now, or Skip this version; `backstep update` does it by hand, `BACKSTEP_UPDATE_CHECK=0` turns the check off).
|
|
59
59
|
|
|
@@ -76,7 +76,7 @@ For tools Backstep can't snapshot, a rule names the tool that undoes them. `{{ar
|
|
|
76
76
|
] }
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
`server` is optional and matches `--name`. For claude.ai connectors and servers Claude Code signs in to, put rules in `~/.backstep/rules.json`; `server` is the name as it appears in tool names (`claude_ai_Notion`), and pressing ctrl+
|
|
79
|
+
`server` is optional and matches `--name`. For claude.ai connectors and servers Claude Code signs in to, put rules in `~/.backstep/rules.json`; `server` is the name as it appears in tool names (`claude_ai_Notion`), and pressing ctrl+u runs the opposite call through Claude Code. A rule overrides the built-in guess, so a `send_message` with a retract rule is undoable. The undo runs through a live `backstep wrap` of that server; if none is running, the dashboard tells you to start it, and any running copy with the same `--name` picks up the request. `examples/rules.json` has GitHub rules as a starting point. Check the tool and argument names against your server's version.
|
|
80
80
|
|
|
81
81
|
## How it decides
|
|
82
82
|
|
|
@@ -101,7 +101,7 @@ Everything is journaled to `~/.backstep/journal.db` (SQLite, mode 0600; override
|
|
|
101
101
|
- Undoing a table restore runs `DELETE` and then re-inserts rows, so foreign keys from other tables pointing at deleted rows can block it. The undo then fails cleanly inside a transaction.
|
|
102
102
|
- `--pg` must point at the same database the wrapped server uses.
|
|
103
103
|
- The dashboard only accepts requests from this machine. The journal stores the `--pg` URL so undo works later.
|
|
104
|
-
- Remote servers that need an OAuth login can't be wrapped; they're watched from Claude Code instead, so their undo works with ctrl+
|
|
104
|
+
- Remote servers that need an OAuth login can't be wrapped; they're watched from Claude Code instead, so their undo works with ctrl+u but not from the dashboard.
|
|
105
105
|
- Shell snapshots cover the project folder (the git repository, or the folder Claude Code runs in). Files outside it, and ignored folders like `node_modules`, aren't saved: undo restores `package.json` and the lockfile, and you re-run the install.
|
|
106
106
|
- Shell commands are judged before they run by what they look like, so a script that pushes or deploys from inside is snapshotted, not held.
|
|
107
107
|
|
package/dist/cli.js
CHANGED
|
@@ -53,7 +53,7 @@ function envHeaders() {
|
|
|
53
53
|
}
|
|
54
54
|
const HELP = `backstep: undo for everything your agents touch
|
|
55
55
|
|
|
56
|
-
backstep setup install the Claude Code mod, bind ctrl+
|
|
56
|
+
backstep setup install the Claude Code mod, bind ctrl+u to undo Claude's last turn,
|
|
57
57
|
and put your MCP servers behind Backstep
|
|
58
58
|
backstep unwrap take your MCP servers back out from behind Backstep
|
|
59
59
|
backstep connect [supabase|firebase] [<access-token>|<connection-string>] [--project <ref>] [--dir <folder>] [--json]
|
package/dist/keys.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// The key that undoes Claude's last turn, in Claude Code's ~/.claude/keybindings.json.
|
|
2
2
|
// The mod's undo button borrows a keybinding action only the diff panel uses
|
|
3
3
|
// (mod buttons can't have actions of their own yet), and whatever key is bound
|
|
4
|
-
// to it, in Global and Chat, presses the button. ctrl+
|
|
4
|
+
// to it, in Global and Chat, presses the button. ctrl+u by default (ctrl+z can't
|
|
5
|
+
// be: Claude Code suspends itself on it before any binding sees it); the person
|
|
5
6
|
// can ask Claude for another, which runs `backstep key <key>`.
|
|
6
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
9
|
import { join } from "node:path";
|
|
9
10
|
export const UNDO_ACTION = "app:cycleDiffBase";
|
|
10
|
-
export const DEFAULT_KEY = "ctrl+
|
|
11
|
+
export const DEFAULT_KEY = "ctrl+u";
|
|
12
|
+
// Bound by Backstep before it knew Claude Code suspends on it; moved to the default on update.
|
|
13
|
+
export const OLD_KEY = "ctrl+z";
|
|
11
14
|
export const defaultKeys = () => [DEFAULT_KEY];
|
|
12
15
|
const CONTEXTS = ["Global", "Chat"];
|
|
13
16
|
// Claude Code never hands these to anything else.
|
|
@@ -41,6 +44,8 @@ export function normalizeKey(raw) {
|
|
|
41
44
|
throw new Error(`"${raw}" on its own would type a letter. Use a modifier, like ctrl+${last}`);
|
|
42
45
|
if (RESERVED.has(key))
|
|
43
46
|
throw new Error(`Claude Code keeps ${key} for itself. Pick another, like ctrl+u`);
|
|
47
|
+
if (key === OLD_KEY || key === "ctrl+shift+z")
|
|
48
|
+
throw new Error(`Claude Code suspends itself on ${key} in a terminal, before Backstep sees it. Pick another, like ctrl+u`);
|
|
44
49
|
return key;
|
|
45
50
|
}
|
|
46
51
|
// Binds undo to `keys` (one, or several) and unbinds the old ones. Refuses a key that does something else, unless it was undo's.
|
|
@@ -83,3 +88,19 @@ export function setUndoKey(raw) {
|
|
|
83
88
|
export function keyLabel(k) {
|
|
84
89
|
return k.startsWith("cmd+") ? `⌘${k.slice(4).toUpperCase()}` : k;
|
|
85
90
|
}
|
|
91
|
+
// As setup runs: keeps a key the person picked, binds ctrl+u on a first
|
|
92
|
+
// install, and moves an install that had ctrl+z onto it. Returns a problem to
|
|
93
|
+
// show, or "".
|
|
94
|
+
export function ensureUndoKey() {
|
|
95
|
+
const keys = undoKeys();
|
|
96
|
+
if (keys.length && !keys.includes(OLD_KEY))
|
|
97
|
+
return "";
|
|
98
|
+
const rest = keys.filter((k) => k !== OLD_KEY);
|
|
99
|
+
try {
|
|
100
|
+
setUndoKey(rest.length ? rest : defaultKeys());
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return err instanceof Error ? err.message : String(err);
|
|
105
|
+
}
|
|
106
|
+
}
|
package/dist/setup.js
CHANGED
|
@@ -3,7 +3,7 @@ import { cpSync, readFileSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { homeDir } from "./store.js";
|
|
6
|
-
import { defaultKeys,
|
|
6
|
+
import { defaultKeys, ensureUndoKey, keyLabel, undoKeys } from "./keys.js";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
8
8
|
import { autoWrap, wrapServers } from "./servers.js";
|
|
9
9
|
import { check, printCheck } from "./check.js";
|
|
@@ -29,18 +29,6 @@ function keepCopy() {
|
|
|
29
29
|
cpSync(dirname(ROOT), modules, { recursive: true });
|
|
30
30
|
return { root: join(modules, "backstep"), onPath: false };
|
|
31
31
|
}
|
|
32
|
-
// Keeps a key the person picked; binds ctrl+z on a first install.
|
|
33
|
-
function bindUndoKey() {
|
|
34
|
-
if (undoKey())
|
|
35
|
-
return "";
|
|
36
|
-
try {
|
|
37
|
-
setUndoKey(defaultKeys());
|
|
38
|
-
return "";
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
return err instanceof Error ? err.message : String(err);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
32
|
const WHERE = `In Supabase, open your project, press Connect at the top, and copy the URI under
|
|
45
33
|
"Direct connection" (or "Session pooler" if your network has no IPv6). Put your
|
|
46
34
|
database password in place of [YOUR-PASSWORD].`;
|
|
@@ -80,7 +68,7 @@ export async function setup() {
|
|
|
80
68
|
if (!r.ok)
|
|
81
69
|
throw new Error(`claude plugin install failed:\n${r.out}`);
|
|
82
70
|
// Only a problem is worth a line here; the summary below says the rest.
|
|
83
|
-
const bound =
|
|
71
|
+
const bound = ensureUndoKey();
|
|
84
72
|
if (bound)
|
|
85
73
|
console.log(` ${white("!")} ${bound}`);
|
|
86
74
|
// Wrap last, so a failed install never leaves servers pointing at a Backstep that isn't there.
|
|
@@ -89,7 +77,7 @@ export async function setup() {
|
|
|
89
77
|
// What's covered and what isn't, said once here and kept up to date by `backstep status`.
|
|
90
78
|
const key = (undoKeys().length ? undoKeys() : defaultKeys()).map(keyLabel).join(" or ");
|
|
91
79
|
console.log(`\n ${pink("✓")} ${white("Backstep is ready.")} Restart Claude Code, then press ${white(key)} after any turn to undo it.`);
|
|
92
|
-
console.log(dim(` Want a different key? Just ask Claude: "change my Backstep undo key to ctrl+
|
|
80
|
+
console.log(dim(` Want a different key? Just ask Claude: "change my Backstep undo key to ctrl+q"`));
|
|
93
81
|
printCheck(check(process.cwd()), bs);
|
|
94
82
|
if (telemetryOn()) {
|
|
95
83
|
console.log(dim(" Sends an anonymous install count. Turn it off with BACKSTEP_TELEMETRY=0.\n"));
|
package/dist/undo.js
CHANGED
|
@@ -111,7 +111,7 @@ async function undoInverse(store, a, u) {
|
|
|
111
111
|
// Claude Code reports back how the call went with `backstep settle`.
|
|
112
112
|
function undoEngine(store, a, u, opts) {
|
|
113
113
|
if (!opts.viaClaudeCode) {
|
|
114
|
-
return { id: a.id, tool: a.tool, ok: false, detail: `undo this
|
|
114
|
+
return { id: a.id, tool: a.tool, ok: false, detail: `undo this from Claude Code (ctrl+u): only it is connected to ${u.server}` };
|
|
115
115
|
}
|
|
116
116
|
if (!store.transition(a.id, a.status, "undo_requested")) {
|
|
117
117
|
return { id: a.id, tool: a.tool, ok: false, detail: "this action changed while undoing; try again" };
|
|
@@ -119,7 +119,7 @@ test('a change that cannot be undone stops the rewind and says so', { options: {
|
|
|
119
119
|
expect(prompts).toHaveLength(1)
|
|
120
120
|
})
|
|
121
121
|
|
|
122
|
-
test('with nothing to undo the band still holds the key,
|
|
122
|
+
test('with nothing to undo the band still holds the key, and a press says there is nothing to undo', async ($, on) => {
|
|
123
123
|
engine(on, { exitCode: 0, stdout: '[]' })
|
|
124
124
|
const toasts: string[] = []
|
|
125
125
|
on('ui.toast', (_, e) => {
|
|
@@ -502,7 +502,7 @@ test('asking Claude to change the undo key rebinds it and relabels the band', {
|
|
|
502
502
|
const runs: string[][] = []
|
|
503
503
|
on('process.run', (_, e) => {
|
|
504
504
|
runs.push([...e.argv])
|
|
505
|
-
const stdout = e.argv.includes('key') ? JSON.stringify({ ok: true, key: 'ctrl+
|
|
505
|
+
const stdout = e.argv.includes('key') ? JSON.stringify({ ok: true, key: 'ctrl+q', previous: 'ctrl+u' }) : '[]'
|
|
506
506
|
return { value: { exitCode: 0, stdout, stderr: '', isStdoutTruncated: false, isStderrTruncated: false } }
|
|
507
507
|
})
|
|
508
508
|
on('ui.toast', () => ({ value: undefined }))
|
|
@@ -510,11 +510,11 @@ test('asking Claude to change the undo key rebinds it and relabels the band', {
|
|
|
510
510
|
const { Text } = $.ui.resolve(e)
|
|
511
511
|
return <Text>engine</Text>
|
|
512
512
|
})
|
|
513
|
-
const r = await $.tool.call({ tool: 'mcp__backstep__undo_key', key: 'Ctrl +
|
|
514
|
-
expect(runs[0].slice(3)).toEqual(['key', 'Ctrl +
|
|
515
|
-
expect(r.result).toBe('Undo is now ctrl+
|
|
513
|
+
const r = await $.tool.call({ tool: 'mcp__backstep__undo_key', key: 'Ctrl + Q' } as never)
|
|
514
|
+
expect(runs[0].slice(3)).toEqual(['key', 'Ctrl + Q', '--json'])
|
|
515
|
+
expect(r.result).toBe('Undo is now ctrl+q (it was ctrl+u). It works right away.')
|
|
516
516
|
const ui = await $.ui.mount({ ...BAND, surface: 'terminal' })
|
|
517
|
-
expect((await ui.find({ type: 'Button', key: 'undo' }))?.text).toBe('ctrl+
|
|
517
|
+
expect((await ui.find({ type: 'Button', key: 'undo' }))?.text).toBe('ctrl+q to undo')
|
|
518
518
|
await ui.unmount()
|
|
519
519
|
})
|
|
520
520
|
|
|
@@ -528,7 +528,7 @@ test('one press only asks; it lets go after a few seconds, or on cancel, and und
|
|
|
528
528
|
|
|
529
529
|
await ui.press({ key: 'undo' })
|
|
530
530
|
expect((await ui.find({ type: 'Text', text: /Undo Claude's last turn\?/ }))).toBeTruthy()
|
|
531
|
-
expect((await ui.find({ type: 'Button', key: 'undo' }))?.text).toBe('ctrl+
|
|
531
|
+
expect((await ui.find({ type: 'Button', key: 'undo' }))?.text).toBe('ctrl+u again to undo')
|
|
532
532
|
await clock.advance(6000)
|
|
533
533
|
expect((await ui.find({ type: 'Text', text: /Last turn/ }))).toBeTruthy()
|
|
534
534
|
|
|
@@ -541,9 +541,9 @@ test('one press only asks; it lets go after a few seconds, or on cancel, and und
|
|
|
541
541
|
await ui.unmount()
|
|
542
542
|
})
|
|
543
543
|
|
|
544
|
-
test('
|
|
544
|
+
test('with two keys bound the band names both', { options: { cli: CLI } }, async ($, on) => {
|
|
545
545
|
on('process.run', (_, e) => {
|
|
546
|
-
const stdout = e.argv.includes('key') ? JSON.stringify({ key: '
|
|
546
|
+
const stdout = e.argv.includes('key') ? JSON.stringify({ key: 'ctrl+u', keys: ['ctrl+u', 'ctrl+q'] }) : '[]'
|
|
547
547
|
return { value: { exitCode: 0, stdout, stderr: '', isStdoutTruncated: false, isStderrTruncated: false } }
|
|
548
548
|
})
|
|
549
549
|
const logs: string[] = []
|
|
@@ -559,8 +559,8 @@ test('on a Mac the band names both keys', { options: { cli: CLI } }, async ($, o
|
|
|
559
559
|
return <Text>engine</Text>
|
|
560
560
|
})
|
|
561
561
|
await $.session.start({ cwd: '/repo', surface: 'terminal', hasPerson: true } as never)
|
|
562
|
-
expect(logs[0]).toContain('press
|
|
562
|
+
expect(logs[0]).toContain('press ctrl+u / ctrl+q to undo')
|
|
563
563
|
const ui = await $.ui.mount({ ...BAND, surface: 'terminal' })
|
|
564
|
-
expect((await ui.find({ type: 'Button', key: 'undo' }))?.text).toBe('
|
|
564
|
+
expect((await ui.find({ type: 'Button', key: 'undo' }))?.text).toBe('ctrl+u / ctrl+q to undo')
|
|
565
565
|
await ui.unmount()
|
|
566
566
|
})
|
package/mod/hooks/register.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Backstep for Claude Code: snapshot every file Claude is about to change,
|
|
2
2
|
// and the whole project before each shell command, and rewind its whole last
|
|
3
|
-
// turn with one key (ctrl+
|
|
3
|
+
// turn with one key (ctrl+u) and a short animation above the prompt. Database
|
|
4
4
|
// and tool changes made through servers wrapped by `backstep wrap`, and
|
|
5
5
|
// Supabase changes over a connected database, rewind with it, by the turn's
|
|
6
6
|
// time window.
|
|
@@ -18,7 +18,7 @@ const CONFIRM_MS = 5000
|
|
|
18
18
|
|
|
19
19
|
// The engine has no keybinding actions of its own for plugins yet, so the undo
|
|
20
20
|
// button borrows one that only the diff panel handles; ~/.claude/keybindings.json
|
|
21
|
-
// binds ctrl+
|
|
21
|
+
// binds ctrl+u to it everywhere else.
|
|
22
22
|
const UNDO_ACTION = 'app:cycleDiffBase'
|
|
23
23
|
const EDIT_TOOLS = new Set(['Edit', 'Write', 'NotebookEdit'])
|
|
24
24
|
const KEEP_TURNS = 30
|
|
@@ -276,7 +276,7 @@ type ShellPlan = { kind: 'read' | 'write' | 'outbound'; reason?: string; id?: st
|
|
|
276
276
|
// Set from the plugin's options each time the module registers.
|
|
277
277
|
let cli = 'backstep'
|
|
278
278
|
// The keys bound to undo, read as a session starts and when the person changes them.
|
|
279
|
-
let undoKeys = ['ctrl+
|
|
279
|
+
let undoKeys = ['ctrl+u']
|
|
280
280
|
const keyLabel = (k: string) => (k.startsWith('cmd+') ? `⌘${k.slice(4).toUpperCase()}` : k)
|
|
281
281
|
const keysLabel = () => undoKeys.map(keyLabel).join(' / ')
|
|
282
282
|
|
|
@@ -320,7 +320,7 @@ async function wrapNewServers($: EngineInterface, cwd: string) {
|
|
|
320
320
|
|
|
321
321
|
// As a session starts: connects what the project's own settings allow, then
|
|
322
322
|
// says what Claude reaches that Backstep could undo once connected, so nobody
|
|
323
|
-
// presses
|
|
323
|
+
// presses undo expecting it to come back.
|
|
324
324
|
async function checkCoverage($: EngineInterface, cwd: string) {
|
|
325
325
|
// Supabase is only ever noticed here, never connected: that's the person's
|
|
326
326
|
// call. Firebase is added once someone is signed in to it.
|
|
@@ -419,13 +419,13 @@ const KEY = 'mcp__backstep__undo_key'
|
|
|
419
419
|
const KEY_TOOL = {
|
|
420
420
|
name: 'undo_key',
|
|
421
421
|
description:
|
|
422
|
-
"Change the key that undoes Claude's last turn with Backstep (ctrl+
|
|
422
|
+
"Change the key that undoes Claude's last turn with Backstep (ctrl+u unless the user changed it; ctrl+z can't be used, Claude Code suspends on it). " +
|
|
423
423
|
'Use it when the user asks to change, rebind or pick a different Backstep or undo shortcut. ' +
|
|
424
424
|
'Keys are written like ctrl+u, cmd+z (⌘ on a Mac; most Mac terminals keep ⌘ keys for themselves, so pair it with a ctrl key), meta+z (Option) or ctrl+shift+z. ' +
|
|
425
425
|
'Several keys can share undo: separate them with commas. It takes effect right away, no restart.',
|
|
426
426
|
inputSchema: {
|
|
427
427
|
type: 'object',
|
|
428
|
-
properties: { key: { type: 'string', description: 'the new key, like ctrl+u, or several separated by commas, like "
|
|
428
|
+
properties: { key: { type: 'string', description: 'the new key, like ctrl+u, or several separated by commas, like "ctrl+u, ctrl+q"' } },
|
|
429
429
|
required: ['key'],
|
|
430
430
|
},
|
|
431
431
|
}
|
|
@@ -452,7 +452,7 @@ const CONNECT = 'mcp__backstep__connect'
|
|
|
452
452
|
const CONNECT_TOOL = {
|
|
453
453
|
name: 'connect',
|
|
454
454
|
description:
|
|
455
|
-
"Connect Backstep (the undo for this session's changes) to the services the project uses, so changes Claude makes to them can be undone with ctrl+
|
|
455
|
+
"Connect Backstep (the undo for this session's changes) to the services the project uses, so changes Claude makes to them can be undone with ctrl+u. " +
|
|
456
456
|
'Use it when the user asks to connect Backstep, or to make their database or Firebase undoable. ' +
|
|
457
457
|
"Supabase: finds the database's connection string in the project's .env files or its local `supabase start` database. " +
|
|
458
458
|
'When it reports it needs a sign-in, tell the user how to make an access token (it says where), ask them to paste it, then call this again with connection_string. ' +
|
|
@@ -640,7 +640,7 @@ export const register: Register = (on, options) => {
|
|
|
640
640
|
|
|
641
641
|
// Idle: a quiet line naming what one press would take back. It's always
|
|
642
642
|
// there, because the button has to be on screen for the key to reach it;
|
|
643
|
-
//
|
|
643
|
+
// with nothing to undo, a press says so rather than doing nothing.
|
|
644
644
|
return (
|
|
645
645
|
<Box>
|
|
646
646
|
<Text color="success">↺ </Text>
|