backstep 0.1.6 → 0.1.7
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/mod/.claude-plugin/plugin.json +1 -1
- package/mod/hooks/register.tsx +27 -20
- package/mod/types/index.d.ts +2 -1
- package/package.json +1 -1
package/mod/hooks/register.tsx
CHANGED
|
@@ -13,6 +13,11 @@ const turns = atom({ plugin: 'backstep', key: 'turns' } as const, [] as Turn[])
|
|
|
13
13
|
const rewind = atom({ plugin: 'backstep', key: 'rewind' } as const, null as Rewind | null)
|
|
14
14
|
const note = atom({ plugin: 'backstep', key: 'note' } as const, null as string | null)
|
|
15
15
|
const armed = atom({ plugin: 'backstep', key: 'armed' } as const, null as number | null)
|
|
16
|
+
// The keys bound to undo, read as a session starts and when the person changes
|
|
17
|
+
// them. Kept in state, not a variable, so the band redraws with the new key.
|
|
18
|
+
const keys = atom({ plugin: 'backstep', key: 'keys' } as const, ['ctrl+u'] as string[])
|
|
19
|
+
// Backstep's mark, two rewind triangles, in the terminal's own black or white.
|
|
20
|
+
const MARK = '◀◀'
|
|
16
21
|
// How long the "press again to undo" question waits before it lets go.
|
|
17
22
|
const CONFIRM_MS = 5000
|
|
18
23
|
|
|
@@ -186,8 +191,9 @@ async function undoLastTurn($: EngineInterface) {
|
|
|
186
191
|
|
|
187
192
|
if (done) {
|
|
188
193
|
const list = items.filter((i) => i.ok).map((i) => `- ${i.label}`).join('\n')
|
|
194
|
+
const key = await keysLabel($)
|
|
189
195
|
await update($, note, () =>
|
|
190
|
-
`[Backstep] The user pressed undo (${
|
|
196
|
+
`[Backstep] The user pressed undo (${key}) on your previous turn. These changes were reverted:\n${list}\n` +
|
|
191
197
|
(lost ? `${plural(lost, 'action')} from that turn couldn't be undone (they reached outside this machine with no way back, or the project was too big to snapshot).\n` : '') +
|
|
192
198
|
`Treat them as not having happened, re-read files before editing them, and don't redo them unless asked.`,
|
|
193
199
|
)
|
|
@@ -275,10 +281,8 @@ type ShellPlan = { kind: 'read' | 'write' | 'outbound'; reason?: string; id?: st
|
|
|
275
281
|
|
|
276
282
|
// Set from the plugin's options each time the module registers.
|
|
277
283
|
let cli = 'backstep'
|
|
278
|
-
// The keys bound to undo, read as a session starts and when the person changes them.
|
|
279
|
-
let undoKeys = ['ctrl+u']
|
|
280
284
|
const keyLabel = (k: string) => (k.startsWith('cmd+') ? `⌘${k.slice(4).toUpperCase()}` : k)
|
|
281
|
-
const keysLabel = () =>
|
|
285
|
+
const keysLabel = async ($: EngineInterface) => (await read($, keys)).map(keyLabel).join(' / ')
|
|
282
286
|
|
|
283
287
|
function backstep(args: string[]): string[] {
|
|
284
288
|
return cli === 'backstep' ? ['backstep', ...args] : ['node', '--disable-warning=ExperimentalWarning', cli, ...args]
|
|
@@ -441,10 +445,12 @@ async function changeKey($: EngineInterface, key: string): Promise<string> {
|
|
|
441
445
|
r = { ok: false, detail: ran.stderr.trim() || 'it failed' }
|
|
442
446
|
}
|
|
443
447
|
if (!r.ok || !r.key) return `Couldn't change the undo key: ${r.detail ?? 'it failed'}`
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
+
const now = r.keys?.length ? r.keys : [r.key]
|
|
449
|
+
await update($, keys, () => now)
|
|
450
|
+
const label = await keysLabel($)
|
|
451
|
+
$.ui.toast(`Backstep: undo is now ${label}.`)
|
|
452
|
+
const was = r.previous && !now.includes(r.previous) ? ` (it was ${keyLabel(r.previous)})` : ''
|
|
453
|
+
return `Undo is now ${label}${was}. It works right away.`
|
|
448
454
|
}
|
|
449
455
|
|
|
450
456
|
const CONNECT = 'mcp__backstep__connect'
|
|
@@ -499,15 +505,15 @@ export const register: Register = (on, options) => {
|
|
|
499
505
|
await $.tool.register(CONNECT_TOOL)
|
|
500
506
|
await $.tool.register(KEY_TOOL)
|
|
501
507
|
const r = await next(e)
|
|
502
|
-
const
|
|
508
|
+
const boundOut = await $.process.run(backstep(['key', '--json'])).catch(() => null)
|
|
503
509
|
try {
|
|
504
|
-
const
|
|
505
|
-
if (
|
|
510
|
+
const bound = (JSON.parse(boundOut?.stdout || '{}') as { keys?: string[] }).keys
|
|
511
|
+
if (bound?.length) await update($, keys, () => bound)
|
|
506
512
|
} catch {
|
|
507
513
|
// keep the default
|
|
508
514
|
}
|
|
509
515
|
// Said first thing, so nobody has to wonder whether it's running.
|
|
510
|
-
$.ui.log(`✓ Backstep connected · press ${keysLabel()} to undo Claude's last turn`)
|
|
516
|
+
$.ui.log(`✓ Backstep connected · press ${await keysLabel($)} to undo Claude's last turn`)
|
|
511
517
|
// In the background: a session never waits on either.
|
|
512
518
|
void wrapNewServers($, e.cwd)
|
|
513
519
|
// One question at a time: Supabase first, then any update.
|
|
@@ -592,15 +598,15 @@ export const register: Register = (on, options) => {
|
|
|
592
598
|
if (r?.phase === 'running') {
|
|
593
599
|
return (
|
|
594
600
|
<Box>
|
|
595
|
-
<Text
|
|
596
|
-
<Text
|
|
601
|
+
<Text bold>{MARK} Rewinding </Text>
|
|
602
|
+
<Text>{bar(r.frame)}</Text>
|
|
597
603
|
<Text dimColor> undoing the last turn…</Text>
|
|
598
604
|
</Box>
|
|
599
605
|
)
|
|
600
606
|
}
|
|
601
607
|
|
|
602
608
|
if (r) {
|
|
603
|
-
const tone = r.phase === 'failed' ? 'error' : r.phase === 'empty' ? 'warning' :
|
|
609
|
+
const tone = r.phase === 'failed' ? 'error' : r.phase === 'empty' ? 'warning' : undefined
|
|
604
610
|
const glyph = r.phase === 'failed' ? '✗' : r.phase === 'empty' ? '!' : '✓'
|
|
605
611
|
const visible = r.items.slice(0, Math.min(r.shown, 6))
|
|
606
612
|
const more = r.items.length - 6
|
|
@@ -612,7 +618,7 @@ export const register: Register = (on, options) => {
|
|
|
612
618
|
{visible.map((i, n) => (
|
|
613
619
|
<Text key={`i${n}`} color={i.ok ? undefined : 'error'} dimColor={i.ok}>
|
|
614
620
|
{' '}
|
|
615
|
-
{i.ok ? '
|
|
621
|
+
{i.ok ? '◀ ' : '✗ '}
|
|
616
622
|
{i.label}
|
|
617
623
|
</Text>
|
|
618
624
|
))}
|
|
@@ -622,13 +628,14 @@ export const register: Register = (on, options) => {
|
|
|
622
628
|
}
|
|
623
629
|
|
|
624
630
|
const target = (await read($, turns)).findLast(hasWork)
|
|
631
|
+
const label = (await read($, keys)).map(keyLabel).join(' / ')
|
|
625
632
|
if (target && (await read($, armed)) !== null) {
|
|
626
633
|
return (
|
|
627
634
|
<Box>
|
|
628
|
-
<Text color="warning" bold
|
|
635
|
+
<Text color="warning" bold>{MARK} Undo Claude's last turn? </Text>
|
|
629
636
|
<Text dimColor>{summary(target)} </Text>
|
|
630
637
|
<Button key="undo" action={UNDO_ACTION} plain onPress={() => void pressUndo($)}>
|
|
631
|
-
{`${
|
|
638
|
+
{`${label} again to undo`}
|
|
632
639
|
</Button>
|
|
633
640
|
<Text dimColor> · </Text>
|
|
634
641
|
<Button key="cancel" plain dimColor onPress={() => void update($, armed, () => null)}>
|
|
@@ -643,10 +650,10 @@ export const register: Register = (on, options) => {
|
|
|
643
650
|
// with nothing to undo, a press says so rather than doing nothing.
|
|
644
651
|
return (
|
|
645
652
|
<Box>
|
|
646
|
-
<Text
|
|
653
|
+
<Text bold>{MARK} </Text>
|
|
647
654
|
<Text dimColor>{target ? `Last turn: ${summary(target)} ` : 'Backstep is on · nothing to undo yet '}</Text>
|
|
648
655
|
<Button key="undo" action={UNDO_ACTION} plain dimColor onPress={() => void pressUndo($)}>
|
|
649
|
-
{`${
|
|
656
|
+
{`${label} to undo`}
|
|
650
657
|
</Button>
|
|
651
658
|
</Box>
|
|
652
659
|
)
|
package/mod/types/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type Rewind = {
|
|
|
26
26
|
declare module 'claude-code' {
|
|
27
27
|
interface PluginState {
|
|
28
28
|
// armed: when the undo key was first pressed; a second press within a few seconds undoes.
|
|
29
|
-
|
|
29
|
+
// keys: the keys bound to undo, kept here so the band redraws when they change.
|
|
30
|
+
backstep: { turns: Turn[]; rewind: Rewind | null; note: string | null; armed: number | null; keys: string[] }
|
|
30
31
|
}
|
|
31
32
|
}
|