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 { Args, On } from 'claude-code' |
| 2 | import { mock } from 'claude-code/testing' |
| 3 | |
| 4 | import { ACCEPTED } from './accepted.js' |
| 5 | import { BEARER } from './bearer.js' |
| 6 | import { CONFIG_PATH } from './config-path.js' |
| 7 | import { ENGINE_VERSION } from './engine-version.js' |
| 8 | import { GLOBAL_CONFIG } from './global-config.js' |
| 9 | import { LISTING } from './listing.js' |
| 10 | import { PROBED } from './probed.js' |
| 11 | import { REPO } from './repo.js' |
| 12 | import type { SentSession } from './sent-session.js' |
| 13 | import type { SessionOptions } from './session-options.js' |
| 14 | |
| 15 | /** |
| 16 | * Answers, beneath the plugins, everything the telemetry plugin reads of a |
| 17 | * session signed in first party on a Mac in a git checkout, on a mock clock. |
| 18 | * |
| 19 | * Keeps what the plugin did: each post, file read, program run and debug |
| 20 | * line, as it does it. The engine answers its version unless told not to. |
| 21 | * |
| 22 | * @param on the test's `on` |
| 23 | * @param options how this session differs from the plain one |
| 24 | * @returns what the plugin did, and the clock |
| 25 | */ |
| 26 | export function firstPartySession( |
| 27 | on: On, |
| 28 | options: SessionOptions = {}, |
| 29 | ): SentSession { |
| 30 | const posts: Args<'http.fetch'>[] = [] |
| 31 | const reads: string[] = [] |
| 32 | const runs: Args<'process.run'>[] = [] |
| 33 | const lines: string[] = [] |
| 34 | const clock = mock.clock(on) |
| 35 | |
| 36 | const files: Readonly<Record<string, string>> = { |
| 37 | [CONFIG_PATH]: GLOBAL_CONFIG, |
| 38 | ...options.files, |
| 39 | } |
| 40 | |
| 41 | const answers = options.answers ?? [ACCEPTED] |
| 42 | const authorizations = options.authorizations ?? [BEARER] |
| 43 | |
| 44 | let authorizeCalls = 0 |
| 45 | |
| 46 | const isPolicyUnreadable = options.policy?.kind === 'unreadable' |
| 47 | |
| 48 | on('session.start', ($, e) => ({ cwd: e.cwd })) |
| 49 | on('session.end', ($, e) => ({ sessionId: e.sessionId })) |
| 50 | on('session.id', () => ({ value: 'the-session' })) |
| 51 | on('session.model', () => ({ value: 'the-model' })) |
| 52 | on('session.surfaces', () => ({ value: ['terminal'] })) |
| 53 | on('session.cwd', () => ({ value: '/work' })) |
| 54 | on('session.repo', () => ({ value: REPO })) |
| 55 | on('fs.list', () => ({ value: LISTING })) |
| 56 | |
| 57 | const engineVersion = options.engineVersion ?? ENGINE_VERSION |
| 58 | |
| 59 | if (engineVersion !== 'unanswered') { |
| 60 | on('session.version', () => ({ value: engineVersion })) |
| 61 | } |
| 62 | |
| 63 | on('session.authorize', () => { |
| 64 | authorizeCalls += 1 |
| 65 | |
| 66 | const answer = |
| 67 | authorizations[Math.min(authorizeCalls, authorizations.length) - 1] |
| 68 | |
| 69 | const isRefused = answer === 'refused' |
| 70 | |
| 71 | return isRefused |
| 72 | ? { deny: 'the credential store is locked' } |
| 73 | : { value: answer === undefined ? BEARER : answer } |
| 74 | }) |
| 75 | |
| 76 | on('settings.read', () => |
| 77 | isPolicyUnreadable |
| 78 | ? { deny: 'the managed settings file is unreadable' } |
| 79 | : { value: options.policy?.settings ?? {} }, |
| 80 | ) |
| 81 | |
| 82 | on('fs.read', ($, e) => { |
| 83 | reads.push(e.path) |
| 84 | |
| 85 | const text = files[e.path] |
| 86 | |
| 87 | return text === undefined ? { deny: `ENOENT: ${e.path}` } : { value: text } |
| 88 | }) |
| 89 | |
| 90 | on('fs.exists', ($, e) => ({ |
| 91 | value: (options.existing ?? []).includes(e.path), |
| 92 | })) |
| 93 | |
| 94 | const probed = { ...PROBED, stdout: options.probeOutput ?? PROBED.stdout } |
| 95 | |
| 96 | on('process.run', ($, e) => { |
| 97 | runs.push(e) |
| 98 | |
| 99 | return { value: probed } |
| 100 | }) |
| 101 | |
| 102 | on('http.fetch', ($, e) => { |
| 103 | posts.push(e) |
| 104 | |
| 105 | return { |
| 106 | value: answers[Math.min(posts.length, answers.length) - 1] ?? ACCEPTED, |
| 107 | } |
| 108 | }) |
| 109 | |
| 110 | on('ui.log', ($, e) => { |
| 111 | if (e.to === 'debug') { |
| 112 | lines.push(e.text) |
| 113 | } |
| 114 | |
| 115 | return { value: undefined } |
| 116 | }) |
| 117 | |
| 118 | return { posts, reads, runs, lines, clock } |
| 119 | } |