surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| 1 | import type { |
| 2 | Args, |
| 3 | CommandRunInput, |
| 4 | On, |
| 5 | ProcessRunResult, |
| 6 | RenderElement, |
| 7 | RenderInput, |
| 8 | SessionStartInput, |
| 9 | } from 'claude-code' |
| 10 | import { |
| 11 | clock, |
| 12 | expect, |
| 13 | memoryStore, |
| 14 | test, |
| 15 | textOf, |
| 16 | tier, |
| 17 | } from 'claude-code/testing' |
| 18 | |
| 19 | tier('builtin') |
| 20 | |
| 21 | const SESSION: SessionStartInput = { |
| 22 | surface: 'terminal', |
| 23 | isInteractive: true, |
| 24 | cwd: '/work', |
| 25 | } |
| 26 | const DIFF: CommandRunInput = { |
| 27 | command: 'diff', |
| 28 | args: '', |
| 29 | origin: { kind: 'composer' }, |
| 30 | } |
| 31 | const CLEAR: CommandRunInput = { |
| 32 | command: 'clear', |
| 33 | args: '', |
| 34 | origin: { kind: 'composer' }, |
| 35 | } |
| 36 | const PANE: RenderInput<'Pane'> = { |
| 37 | component: 'Pane', |
| 38 | surface: 'terminal', |
| 39 | requestId: 'diff', |
| 40 | viewport: { columns: 160, rows: 40 }, |
| 41 | props: { |
| 42 | title: 'Diff', |
| 43 | isFocused: false, |
| 44 | bodyColumns: 80, |
| 45 | placement: 'dock', |
| 46 | scroll: { offset: 0, bodyRows: 30 }, |
| 47 | }, |
| 48 | } |
| 49 | /** |
| 50 | * The prompt's hint on a 160-column terminal: drawing it is how the plugin |
| 51 | * learns how wide the terminal is. |
| 52 | */ |
| 53 | const HINT: RenderInput<'PromptHint'> = { |
| 54 | component: 'PromptHint', |
| 55 | surface: 'terminal', |
| 56 | requestId: 'hint', |
| 57 | viewport: { columns: 160, rows: 40 }, |
| 58 | props: { isDraft: false, isWorking: false, hint: '' }, |
| 59 | } |
| 60 | /** |
| 61 | * What the engine draws for the hint, standing in beneath the plugin. |
| 62 | */ |
| 63 | const HINT_DRAWN: RenderElement = { |
| 64 | type: 'Text', |
| 65 | children: ['? for shortcuts'], |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * A repository at /work with one changed file, as git's output for each |
| 70 | * invocation whose command line holds the key. |
| 71 | */ |
| 72 | const REPOSITORY: Readonly<Record<string, string>> = { |
| 73 | 'rev-parse --path-format=absolute': '/work\n/work/.git\n/work/.git\n', |
| 74 | 'HEAD --shortstat': ' 1 file changed, 1 insertion(+)', |
| 75 | 'HEAD --numstat': '1\t0\tapp.ts\0', |
| 76 | 'ls-files': '', |
| 77 | '-- app.ts': '@@ -1 +1 @@\n-const a = 1\n+const a = 2\n', |
| 78 | } |
| 79 | |
| 80 | /** |
| 81 | * What git answers in that repository; an invocation it does not know |
| 82 | * fails as git does outside a repository. |
| 83 | */ |
| 84 | function gitIn(argv: readonly string[]): ProcessRunResult { |
| 85 | const line = argv.join(' ') |
| 86 | const found = Object.entries(REPOSITORY).find(([key]) => line.includes(key)) |
| 87 | |
| 88 | return found |
| 89 | ? { exitCode: 0, stdout: found[1], stderr: '' } |
| 90 | : { exitCode: 128, stdout: '', stderr: 'fatal: not a git repository' } |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * A session in that repository: /diff registers, git answers, the store |
| 95 | * starts empty, the engine draws the prompt's hint, and what the plugin |
| 96 | * asks of the world (each git run, each pane opened or closed) is kept. |
| 97 | */ |
| 98 | function inRepository(on: On) { |
| 99 | const runs: Args<'process.run'>[] = [] |
| 100 | const opened: Args<'ui.open'>[] = [] |
| 101 | const closed: Args<'ui.close'>[] = [] |
| 102 | on('session.start', ($, e) => ({ cwd: e.cwd })) |
| 103 | on('command.register', ($, e) => ({ value: { command: e.name } })) |
| 104 | on('process.run', ($, e) => { |
| 105 | runs.push(e) |
| 106 | return { value: gitIn(e.argv) } |
| 107 | }) |
| 108 | on('ui.open', ($, e) => { |
| 109 | opened.push(e) |
| 110 | return { value: undefined } |
| 111 | }) |
| 112 | on('ui.close', ($, e) => { |
| 113 | closed.push(e) |
| 114 | return { value: undefined } |
| 115 | }) |
| 116 | on('ui.invalidate', () => ({ value: undefined })) |
| 117 | on('ui.render', { component: 'PromptHint' }, () => HINT_DRAWN) |
| 118 | on('session.messages', () => ({ value: [] })) |
| 119 | memoryStore(on) |
| 120 | |
| 121 | return { runs, opened, closed } |
| 122 | } |
| 123 | |
| 124 | test('/diff opens the pane over the changes in the session', async ($, on) => { |
| 125 | const world = inRepository(on) |
| 126 | |
| 127 | await $.session.start(SESSION) |
| 128 | |
| 129 | expect(await $.command.run(DIFF)).toEqual({}) |
| 130 | expect(world.opened.map(pane => pane.id)).toEqual(['diff']) |
| 131 | |
| 132 | await clock.advance(1000) |
| 133 | const drawn = textOf(await $.ui.render(PANE)) |
| 134 | |
| 135 | expect(drawn).toContain('1 file changed') |
| 136 | expect(drawn).toContain('app.ts') |
| 137 | }) |
| 138 | |
| 139 | test('every git child is pinned to the repository and reads the C locale', async ($, on) => { |
| 140 | const world = inRepository(on) |
| 141 | |
| 142 | await $.session.start(SESSION) |
| 143 | await $.command.run(DIFF) |
| 144 | await clock.advance(1000) |
| 145 | const [discovery, ...pinned] = world.runs |
| 146 | |
| 147 | expect(discovery.argv).toContain('--show-toplevel') |
| 148 | expect(discovery.init?.cwd).toBeUndefined() |
| 149 | expect(pinned.length).toBeGreaterThan(0) |
| 150 | |
| 151 | for (const run of pinned) { |
| 152 | expect(run.argv.slice(0, 3)).toEqual([ |
| 153 | 'git', |
| 154 | '--git-dir=/work/.git', |
| 155 | '--work-tree=/work', |
| 156 | ]) |
| 157 | expect(run.init).toMatchObject({ cwd: '/work', env: { LC_ALL: 'C' } }) |
| 158 | } |
| 159 | }) |
| 160 | |
| 161 | test('the first edit Claude makes opens the pane on a wide terminal', async ($, on) => { |
| 162 | const world = inRepository(on) |
| 163 | on('tool.call', () => ({ result: 'edited' })) |
| 164 | |
| 165 | await $.session.start(SESSION) |
| 166 | await $.ui.render(HINT) |
| 167 | await $.tool.call({ |
| 168 | tool: 'Edit', |
| 169 | file_path: '/work/app.ts', |
| 170 | old_string: '1', |
| 171 | new_string: '2', |
| 172 | }) |
| 173 | await clock.advance(1000) |
| 174 | |
| 175 | expect(world.opened.map(pane => pane.id)).toEqual(['diff']) |
| 176 | }) |
| 177 | |
| 178 | test('/clear closes the pane it finds open', async ($, on) => { |
| 179 | const world = inRepository(on) |
| 180 | on('command.run', { command: 'clear' }, () => ({})) |
| 181 | |
| 182 | await $.session.start(SESSION) |
| 183 | await $.command.run(DIFF) |
| 184 | await $.command.run(CLEAR) |
| 185 | |
| 186 | expect(world.closed.map(pane => pane.id)).toEqual(['diff']) |
| 187 | }) |