@cmodjs/core 0.1.7 → 0.1.9

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.
Files changed (87) hide show
  1. package/README.md +2 -2
  2. package/docs/commands.md +14 -14
  3. package/docs/dependencies.md +1 -1
  4. package/docs/hooks.md +69 -13
  5. package/docs/index.md +7 -5
  6. package/docs/install-steps.md +19 -19
  7. package/docs/jobs.md +22 -21
  8. package/docs/mod.md +44 -12
  9. package/docs/state.md +8 -8
  10. package/docs/testing.md +25 -9
  11. package/docs/ui.md +33 -9
  12. package/jobs/check.js +2 -2
  13. package/jobs/context.d.ts.map +1 -1
  14. package/jobs/context.js +2 -0
  15. package/jobs/permissions/decide-permission.js +2 -2
  16. package/jobs/permissions/find-project-scope.d.ts +1 -1
  17. package/jobs/permissions/find-project-scope.d.ts.map +1 -1
  18. package/jobs/permissions/find-project-scope.js +9 -9
  19. package/jobs/permissions/match-target.d.ts.map +1 -1
  20. package/jobs/permissions/match-target.js +4 -3
  21. package/mod.d.ts +15 -5
  22. package/mod.d.ts.map +1 -1
  23. package/package.json +2 -1
  24. package/path.d.ts +2 -0
  25. package/path.d.ts.map +1 -0
  26. package/path.js +1 -0
  27. package/records.js +3 -3
  28. package/register.d.ts.map +1 -1
  29. package/register.js +8 -2
  30. package/runtime/claude.d.ts +4 -1
  31. package/runtime/claude.d.ts.map +1 -1
  32. package/runtime/hooks.d.ts +10 -2
  33. package/runtime/hooks.d.ts.map +1 -1
  34. package/runtime/hooks.js +56 -18
  35. package/runtime/lifecycle.d.ts.map +1 -1
  36. package/runtime/lifecycle.js +17 -7
  37. package/runtime/ui.d.ts.map +1 -1
  38. package/runtime/ui.js +38 -8
  39. package/shell.d.ts +2 -0
  40. package/shell.d.ts.map +1 -0
  41. package/shell.js +1 -0
  42. package/src/jobs/check.ts +2 -2
  43. package/src/jobs/context.ts +2 -0
  44. package/src/jobs/permissions/decide-permission.ts +2 -2
  45. package/src/jobs/permissions/find-project-scope.ts +11 -10
  46. package/src/jobs/permissions/match-target.ts +4 -3
  47. package/src/mod.ts +22 -3
  48. package/src/path.ts +1 -0
  49. package/src/records.ts +3 -3
  50. package/src/register.ts +8 -2
  51. package/src/runtime/claude.ts +11 -1
  52. package/src/runtime/hooks.ts +62 -23
  53. package/src/runtime/lifecycle.ts +16 -9
  54. package/src/runtime/ui.ts +35 -8
  55. package/src/shell.ts +1 -0
  56. package/src/testing/fake-claude.ts +14 -5
  57. package/src/testing/fake-elements.ts +5 -7
  58. package/src/testing/fake-files.ts +5 -5
  59. package/src/testing.ts +83 -11
  60. package/src/ui/define-pane.ts +5 -1
  61. package/src/utils/call-effects.ts +12 -11
  62. package/src/utils/parse-shell.ts +28 -36
  63. package/src/utils/paths.ts +4 -19
  64. package/src/vendor.ts +1 -0
  65. package/testing/fake-claude.d.ts +5 -0
  66. package/testing/fake-claude.d.ts.map +1 -1
  67. package/testing/fake-claude.js +13 -3
  68. package/testing/fake-elements.d.ts +5 -8
  69. package/testing/fake-elements.d.ts.map +1 -1
  70. package/testing/fake-elements.js +6 -6
  71. package/testing/fake-files.js +5 -5
  72. package/testing.d.ts +4 -2
  73. package/testing.d.ts.map +1 -1
  74. package/testing.js +65 -9
  75. package/ui/define-pane.d.ts +5 -1
  76. package/ui/define-pane.d.ts.map +1 -1
  77. package/utils/call-effects.d.ts.map +1 -1
  78. package/utils/call-effects.js +12 -11
  79. package/utils/parse-shell.d.ts +5 -9
  80. package/utils/parse-shell.d.ts.map +1 -1
  81. package/utils/parse-shell.js +5 -10
  82. package/utils/paths.d.ts +0 -3
  83. package/utils/paths.d.ts.map +1 -1
  84. package/utils/paths.js +3 -18
  85. package/vendor.d.ts +1 -0
  86. package/vendor.d.ts.map +1 -1
  87. package/vendor.js +209 -12
@@ -12,8 +12,9 @@ export type Fakes = {
12
12
  fs: { read?: Claude['fs']['read']; write?: Claude['fs']['write']; list?: Claude['fs']['list']; exists?: Claude['fs']['exists']; stat?: Claude['fs']['stat'] }
13
13
  http: { fetch?: Claude['http']['fetch'] }
14
14
  settings: { read?: Claude['settings']['read'] }
15
- ui: { ask?: Claude['ui']['ask']; open?: Claude['ui']['open'] }
16
- agent: { list?: Claude['agent']['list'] }
15
+ ui: { ask?: Claude['ui']['ask']; open?: Claude['ui']['open']; scroll?: Claude['ui']['scroll'] }
16
+ session: { messages?: Claude['session']['messages'] }
17
+ agent: { list?: Claude['agent']['list']; spawn?: Claude['agent']['spawn'] }
17
18
  clock: { after?: Claude['clock']['after']; every?: Claude['clock']['every'] }
18
19
  cmod: { call?: Claude['cmod']['call'] }
19
20
  }
@@ -39,9 +40,10 @@ export type FakeClaude = {
39
40
 
40
41
  export function fakeClaude(plugin: { readonly name: string; readonly root: string }): FakeClaude {
41
42
  const calls: TestCall[] = []
42
- const fakes: Fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, agent: {}, clock: {}, cmod: {} }
43
+ const fakes: Fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, session: {}, agent: {}, clock: {}, cmod: {} }
43
44
  const shown: Shown = { toasts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [] }
44
45
  const unplacedPanes = new Set<string>()
46
+ let focusedPane: string | undefined
45
47
  const store = new Map<string, unknown>()
46
48
 
47
49
  const faked = <Args extends readonly unknown[], Result>(call: string, answer: () => ((...args: Args) => Result) | undefined) => {
@@ -81,6 +83,7 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
81
83
  if (answer.isPlaced) {
82
84
  shown.openPanes.add(pane.id)
83
85
  unplacedPanes.delete(pane.id)
86
+ if (pane.focus === true) focusedPane = pane.id
84
87
  } else if (!shown.openPanes.has(pane.id)) {
85
88
  unplacedPanes.add(pane.id)
86
89
  }
@@ -90,11 +93,13 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
90
93
  calls.push({ call: 'ui.close', args: [pane] })
91
94
  shown.openPanes.delete(pane.id)
92
95
  unplacedPanes.delete(pane.id)
96
+ if (focusedPane === pane.id) focusedPane = undefined
93
97
  },
94
98
  panes: async () => [
95
- ...[...shown.openPanes].map((id) => ({ id, title: id, isShown: true, isFocused: false, isPlaced: true })),
99
+ ...[...shown.openPanes].map((id) => ({ id, title: id, isShown: true, isFocused: id === focusedPane, isPlaced: true })),
96
100
  ...[...unplacedPanes].map((id) => ({ id, title: id, isShown: false, isFocused: false, isPlaced: false })),
97
101
  ],
102
+ scroll: rejected('ui.scroll', () => fakes.ui.scroll ?? (async () => ({}))),
98
103
  resolve: () => elements,
99
104
  },
100
105
  process: {
@@ -110,7 +115,10 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
110
115
  },
111
116
  http: { fetch: rejected('http.fetch', () => fakes.http.fetch) },
112
117
  settings: { read: rejected('settings.read', () => fakes.settings.read) },
113
- agent: { list: rejected('agent.list', () => fakes.agent.list) },
118
+ agent: {
119
+ list: rejected('agent.list', () => fakes.agent.list),
120
+ spawn: rejected('agent.spawn', () => fakes.agent.spawn),
121
+ },
114
122
  store: {
115
123
  get: async (key) => store.get(key),
116
124
  set: async (key, value) => {
@@ -133,6 +141,7 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
133
141
  model: async () => 'test-model',
134
142
  usage: async () => ({ context: { window: 200000 }, rateLimits: {}, cost: { usd: 0 } }) as unknown as Awaited<ReturnType<Claude['session']['usage']>>,
135
143
  surfaces: async () => ['terminal'],
144
+ messages: rejected('session.messages', () => fakes.session.messages as ((args?: unknown) => Promise<unknown>) | undefined) as Claude['session']['messages'],
136
145
  },
137
146
  command: {
138
147
  register: async (command) => {
@@ -2,8 +2,6 @@ import type { ElementTable, RenderElement, RenderNode } from 'claude-code'
2
2
 
3
3
  type Drawn = { readonly type: string; readonly props: Record<string, unknown>; readonly children?: readonly RenderNode[] }
4
4
 
5
- type Button = { readonly type: 'Button'; readonly props: { readonly key: string; readonly label: string; readonly onPress: () => unknown } }
6
-
7
5
  const boxInsides = new WeakMap<object, Drawn>()
8
6
 
9
7
  function element(type: string) {
@@ -31,12 +29,12 @@ export const elements = {
31
29
  Image: element('Image'),
32
30
  } as unknown as ElementTable
33
31
 
34
- export function findButton(node: RenderNode, key: string): Button | undefined {
32
+ export function findElement(node: RenderNode, type: string, key: string): Drawn['props'] | undefined {
35
33
  if (typeof node === 'string') return undefined
36
- const { type, props, children = [] } = insideOf(node)
37
- if (type === 'Button' && (props['key'] === key || props['label'] === key)) return node as unknown as Button
38
- for (const child of children) {
39
- const found = findButton(child, key)
34
+ const drawn = insideOf(node)
35
+ if (drawn.type === type && (drawn.props['key'] === key || (type === 'Button' && drawn.props['label'] === key))) return drawn.props
36
+ for (const child of drawn.children ?? []) {
37
+ const found = findElement(child, type, key)
40
38
  if (found !== undefined) return found
41
39
  }
42
40
  return undefined
@@ -1,10 +1,10 @@
1
1
  import type { Claude } from '../runtime/claude.js'
2
- import { resolvePath } from '../utils/paths.js'
2
+ import { resolve } from '../vendor.js'
3
3
 
4
4
  export function fakeFiles(files: Readonly<Record<string, string>>, links: Readonly<Record<string, string>> = {}): Claude['fs'] {
5
- const contents = new Map(Object.entries(files).map(([path, text]) => [resolvePath(path, '/'), text]))
5
+ const contents = new Map(Object.entries(files).map(([path, text]) => [resolve('/', path), text]))
6
6
  const realPathOf = (path: string): string => {
7
- const absolute = resolvePath(path, '/')
7
+ const absolute = resolve('/', path)
8
8
  for (const [link, target] of Object.entries(links)) {
9
9
  if (absolute === link || absolute.startsWith(`${link}/`)) return realPathOf(`${target}${absolute.slice(link.length)}`)
10
10
  }
@@ -35,7 +35,7 @@ export function fakeFiles(files: Readonly<Record<string, string>>, links: Readon
35
35
  if (kindOf(folder) !== 'dir') throw new Error(`ENOENT: no such directory, list '${path}'`)
36
36
  const names = new Set(below(folder).map((file) => file.split('/')[0] as string))
37
37
  return [...names].map((name) => {
38
- const entry = resolvePath(name, folder)
38
+ const entry = resolve(folder, name)
39
39
  if (Object.hasOwn(links, entry)) return { name, kind: 'other', size: 0, mtimeMs: 0, isLink: true }
40
40
  return { name, kind: contents.has(entry) ? 'file' : 'dir', size: sizeOf(entry), mtimeMs: 0, isLink: false }
41
41
  })
@@ -44,7 +44,7 @@ export function fakeFiles(files: Readonly<Record<string, string>>, links: Readon
44
44
  stat: async (path, options) => {
45
45
  const realPath = realPathOf(path)
46
46
  const kind = kindOf(realPath)
47
- const isLink = Object.hasOwn(links, resolvePath(path, '/'))
47
+ const isLink = Object.hasOwn(links, resolve('/', path))
48
48
  if (kind === undefined && isLink) return { kind: 'other', size: 0, mtimeMs: 0, isLink }
49
49
  if (kind === undefined) throw new Error(`ENOENT: no such file or directory, stat '${path}'`)
50
50
  return { kind, size: sizeOf(realPath), mtimeMs: 0, isLink, ...(options?.resolve === true ? { realPath } : {}) }
package/src/testing.ts CHANGED
@@ -1,13 +1,31 @@
1
- import type { Args, ClassicHookInputs, CmodDependencies, CommandPresentation, CommandRunResult, EventResult, Frozen, PaneOpenArgs, RenderChildren, RenderComponent, RenderElement, RenderPropsOf } from 'claude-code'
1
+ import type {
2
+ Args,
3
+ ClassicHookInputs,
4
+ CmodDependencies,
5
+ CommandPresentation,
6
+ CommandRunResult,
7
+ EventResult,
8
+ Frozen,
9
+ PaneOpenArgs,
10
+ PressedLink,
11
+ RenderChildren,
12
+ RenderComponent,
13
+ RenderElement,
14
+ RenderPropsOf,
15
+ UiInputArgument,
16
+ UiPressArgument,
17
+ UiSelectArgument,
18
+ } from 'claude-code'
2
19
  import type { Reply } from './jobs/slash-command.js'
3
20
  import type { ModDefinition, ModEvent } from './mod.js'
4
21
  import { storeFolder } from './records.js'
5
22
  import type { Claude } from './runtime/claude.js'
6
23
  import { answerCall, notInstalled } from './runtime/dependencies.js'
7
24
  import type { RoutedEvent } from './runtime/hooks.js'
25
+ import { toolInputOf } from './runtime/tool-calls.js'
8
26
  import { createLifecycle } from './runtime/lifecycle.js'
9
27
  import { fakeClaude, type Fakes, type Shown, type TestCall } from './testing/fake-claude.js'
10
- import { elements, findButton, rowsOf } from './testing/fake-elements.js'
28
+ import { elements, findElement, rowsOf } from './testing/fake-elements.js'
11
29
  import { fakeFiles } from './testing/fake-files.js'
12
30
  import type { Slot, SlotProps } from './ui/slots.js'
13
31
 
@@ -35,7 +53,9 @@ export type TestedMod<State extends object> = {
35
53
  lines<S extends Slot<object, object, { readonly component: RenderComponent }>>(slot: S, props: Omit<SlotProps<S>, 'Default'>, requestId?: string): Promise<string[]>
36
54
  type(line: string): Promise<Reply>
37
55
  callTool(name: string, input: Record<string, unknown>): Promise<EventResult<'tool.call'>>
38
- press(paneId: string, key: string): Promise<void>
56
+ press(paneId: string, key: string, link?: string): Promise<void>
57
+ input(paneId: string, key: string, text: string, kind?: UiInputArgument['kind']): Promise<void>
58
+ select(paneId: string, key: string, value: string): Promise<void>
39
59
  moveTo(projectRoot: string, cwd?: string): Promise<void>
40
60
  readonly state: Readonly<State>
41
61
  readonly calls: readonly TestCall[]
@@ -67,6 +87,12 @@ export function testMod<State extends object>(definition: ModDefinition<State>,
67
87
  fake.claude.session.root = async () => projectRoot
68
88
  fake.claude.session.cwd = async () => cwd
69
89
  fake.fakes.cmod.call = fakeDependencies(options.dependencies ?? {})
90
+ const firedAgents = new Map<string, string>()
91
+ const listAgents = fake.claude.agent.list
92
+ fake.claude.agent.list = async () => {
93
+ if (fake.fakes.agent.list !== undefined || firedAgents.size === 0) return listAgents()
94
+ return [...firedAgents].map(([id, type]) => ({ id, type, description: '', status: 'running' as const }))
95
+ }
70
96
  Object.assign(fake.fakes.fs, fakeFiles(options.files ?? {}))
71
97
  const starting = options.state as Record<string, object> | undefined
72
98
  const declared = Object.entries(definition.state ?? {}) as [string, object][]
@@ -100,11 +126,20 @@ export function testMod<State extends object>(definition: ModDefinition<State>,
100
126
  }
101
127
  const opened = fake.calls.findLast((call) => call.call === 'ui.open' && (call.args[0] as PaneOpenArgs).id === paneId)?.args[0] as PaneOpenArgs | undefined
102
128
  const columns = opened?.columns ?? defaultColumns
103
- const props: RenderPropsOf['Pane'] = { title: opened?.title ?? paneId, isFocused: false, bodyColumns: columns, placement: 'dock', scroll: { offset: 0, bodyRows: defaultRows }, view: {} }
129
+ const isFocused = (await fake.claude.ui.panes()).some((open) => open.id === paneId && open.isFocused)
130
+ const props: RenderPropsOf['Pane'] = { title: opened?.title ?? paneId, isFocused, bodyColumns: columns, placement: 'dock', scroll: { offset: 0, bodyRows: defaultRows }, view: {} }
104
131
  const drawing = await route('ui.render', { surface: 'terminal', component: 'Pane', requestId: paneId, props }, elements.Box({}))
105
132
  return { drawing, columns }
106
133
  }
107
134
 
135
+ const drawnElement = async (paneId: string, type: 'Button' | 'Input' | 'Select' | 'Markdown', key: string) => {
136
+ const props = findElement((await drawPane(paneId)).drawing, type, key)
137
+ if (props === undefined) throw new Error(`The pane "${paneId}" of ${name} draws no ${type} with the ${type === 'Button' ? 'key or label' : 'key'} "${key}".`)
138
+ return props
139
+ }
140
+
141
+ const eventIn = (paneId: string, key: string) => ({ plugin: name, element: key, component: 'Pane' as const, requestId: paneId, surface: 'terminal' as const })
142
+
108
143
  const drawSlot = async (component: RenderComponent, props: object, requestId = `${component}_${(slotDraws += 1)}`) => {
109
144
  await start()
110
145
  const input = { surface: 'terminal', component, requestId, props } as Frozen<Args<'ui.render'>>
@@ -121,11 +156,31 @@ export function testMod<State extends object>(definition: ModDefinition<State>,
121
156
  }
122
157
  const toolUse = toolUseEvents.includes(event) ? { tool_use_id: `toolu_${(toolUses += 1)}` } : {}
123
158
  const filled = { session_id: sessionId, transcript_path: '/test/transcript.jsonl', cwd, hook_event_name: event, ...toolUse, ...input }
124
- if (event !== 'PreToolUse') return route(`classic.${event as ModEvent}`, filled, below)
125
- const { tool_name, tool_input, tool_use_id } = filled as ClassicHookInputs['PreToolUse']
126
- return route('classic.PreToolUse', { ...(tool_input as object), tool: tool_name, tool_use_id }, below)
159
+ if (event !== 'PreToolUse') return route(`classic.${event as Exclude<ModEvent, 'PreToolUse'>}`, filled, below)
160
+ return firePreToolUse(filled as ClassicHookInputs['PreToolUse'], below as { deny?: string } | undefined)
127
161
  }) as TestedMod<State>['fire']
128
162
 
163
+ const firePreToolUse = async ({ tool_name, tool_input, tool_use_id, agent_id, agent_type }: ClassicHookInputs['PreToolUse'], below: { deny?: string } | undefined) => {
164
+ await start()
165
+ if (agent_id !== undefined && agent_type !== undefined) firedAgents.set(agent_id, agent_type)
166
+ const envelope = { ...(tool_input as object), tool: tool_name, tool_use_id, ...(agent_id === undefined ? {} : { agentId: agent_id }) } as Frozen<Args<'tool.call'>>
167
+ const asks: EventResult<'tool.check'> = below?.deny === undefined ? { decision: 'ask' } : { decision: 'deny', reason: below.deny }
168
+ let reached = envelope
169
+ let check = asks
170
+ const answer = await lifecycle.route('tool.call', envelope, async (e) => {
171
+ reached = e
172
+ check = await lifecycle.route('tool.check', { tool: e.tool, input: toolInputOf(e), tool_use_id: e.tool_use_id, ...(e.agentId === undefined ? {} : { agentId: e.agentId }) }, async () => asks)
173
+ return (check.decision === 'deny' ? { deny: check.reason ?? '' } : { result: '' }) as EventResult<'tool.call'>
174
+ })
175
+ const updatedInput = toolInputOf(reached)
176
+ const decided = check === asks ? {} : check.decision === 'allow' ? { allow: true } : check.decision === 'ask' ? { ask: check.reason ?? '' } : {}
177
+ return {
178
+ ...(answer.deny === undefined ? decided : { deny: answer.deny }),
179
+ ...(JSON.stringify(updatedInput) === JSON.stringify(tool_input) ? {} : { updatedInput }),
180
+ ...(answer.context === undefined || answer.context.length === 0 ? {} : { additionalContext: [...answer.context] }),
181
+ } as EventResult<'classic.PreToolUse'>
182
+ }
183
+
129
184
  return {
130
185
  start,
131
186
  fire,
@@ -154,10 +209,27 @@ export function testMod<State extends object>(definition: ModDefinition<State>,
154
209
  if (answer === unanswered) throw new Error(`${name} has no tool ${toolName}. Add it in setup with mod.use(tool({ name: '${toolName}', … })).`)
155
210
  return answer
156
211
  },
157
- async press(paneId, key) {
158
- const button = findButton((await drawPane(paneId)).drawing, key)
159
- if (button === undefined) throw new Error(`The pane "${paneId}" of ${name} draws no Button with the key or label "${key}".`)
160
- await button.props.onPress()
212
+ async press(paneId, key, link) {
213
+ const props = await drawnElement(paneId, link === undefined ? 'Button' : 'Markdown', key)
214
+ if (link !== undefined && props['onLinkPress'] === undefined) throw new Error(`The Markdown "${key}" in the pane "${paneId}" of ${name} has no onLinkPress, so Claude Code opens its links itself.`)
215
+ const pressed: UiPressArgument = { ...eventIn(paneId, String(props['key'])), ...(link === undefined ? {} : { link: { href: link } }) }
216
+ await lifecycle.route('ui.press', pressed as Frozen<Args<'ui.press'>>, async (e) => {
217
+ if (e.link === undefined) await (props['onPress'] as (e: UiPressArgument) => unknown)(e)
218
+ else await (props['onLinkPress'] as (link: PressedLink, e: UiPressArgument) => unknown)(e.link, e)
219
+ return { element: e.element }
220
+ })
221
+ },
222
+ async input(paneId, key, text, kind = 'submit') {
223
+ const props = await drawnElement(paneId, 'Input', key)
224
+ const typed: UiInputArgument = { ...eventIn(paneId, key), kind, value: text }
225
+ await (props[kind === 'submit' ? 'onSubmit' : 'onInput'] as ((value: string, e: UiInputArgument) => unknown) | undefined)?.(text, typed)
226
+ },
227
+ async select(paneId, key, value) {
228
+ const props = await drawnElement(paneId, 'Select', key)
229
+ const options = (props['options'] as readonly { readonly value: string }[]).map((option) => option.value)
230
+ if (!options.includes(value)) throw new Error(`The Select "${key}" in the pane "${paneId}" of ${name} has no option "${value}". Its options are ${options.map((option) => `"${option}"`).join(', ')}.`)
231
+ const picked: UiSelectArgument = { ...eventIn(paneId, key), value }
232
+ await (props['onSelect'] as (value: string, e: UiSelectArgument) => unknown)(value, picked)
161
233
  },
162
234
  async moveTo(nextRoot, nextCwd = nextRoot) {
163
235
  await start()
@@ -1,4 +1,4 @@
1
- import type { Frozen, RenderElement, RenderPropsOf } from 'claude-code'
1
+ import type { Frozen, PaneCloseInput, RenderElement, RenderPropsOf, UiScrollInput } from 'claude-code'
2
2
  import type { Mod } from '../mod.js'
3
3
 
4
4
  export type Pane<State extends object = Record<never, never>> = {
@@ -6,7 +6,11 @@ export type Pane<State extends object = Record<never, never>> = {
6
6
  readonly title: string
7
7
  readonly columns?: number | ((state: State) => number | undefined)
8
8
  readonly rows?: number | ((state: State) => number | undefined)
9
+ readonly closeOnEscape?: true
10
+ readonly holdToasts?: true
9
11
  render(mod: Mod<State>, props: Frozen<RenderPropsOf['Pane']>): RenderElement
12
+ onScroll?(mod: Mod<State>, e: Frozen<UiScrollInput>): void | Promise<void>
13
+ onClose?(mod: Mod<State>, e: Frozen<PaneCloseInput>): void | Promise<void>
10
14
  }
11
15
 
12
16
  const paneId = /^[A-Za-z0-9_-]{1,64}$/
@@ -1,5 +1,6 @@
1
- import { parseShellCommands, type ShellCommand } from './parse-shell.js'
2
- import { expandHome, resolvePath, type FileSystem } from './paths.js'
1
+ import { parseShell, type ShellCommand } from './parse-shell.js'
2
+ import { expandHome, type FileSystem } from './paths.js'
3
+ import { resolve } from '../vendor.js'
3
4
 
4
5
  export type ToolUse = { tool: string; input: unknown; agentId?: string; agentType?: string }
5
6
 
@@ -21,13 +22,13 @@ export type CallEffects = {
21
22
 
22
23
  export function callEffects(use: ToolUse, workspace: { cwd: string; home: string; fs: FileSystem }): CallEffects {
23
24
  const { fs } = workspace
24
- const resolve = (path: string, folder = workspace.cwd) => resolvePath(expandHome(path, workspace.home), folder)
25
- const access = (path: string): FileAccess => ({ path: resolve(path), contents: undefined })
25
+ const absolute = (path: string, folder = workspace.cwd) => resolve(folder, expandHome(path, workspace.home))
26
+ const access = (path: string): FileAccess => ({ path: absolute(path), contents: undefined })
26
27
  const effects: CallEffects = { shell: undefined, reads: [], writes: [], urls: [], subagent: undefined }
27
28
  if (use.tool === 'Bash') {
28
29
  const line = textField(use, 'command')
29
- const parsed = parseShellCommands(line)
30
- const commands = parsed.commands.map(({ argv, folder }) => ({ argv, folder: resolve(folder) }))
30
+ const parsed = parseShell(line)
31
+ const commands = parsed.commands.map(({ argv, folder }) => ({ argv, folder: absolute(folder) }))
31
32
  effects.shell = { line, commands, isFullyParsed: parsed.isFullyParsed }
32
33
  effects.reads = parsed.reads.map(access)
33
34
  effects.writes = parsed.writes.map(access)
@@ -35,22 +36,22 @@ export function callEffects(use: ToolUse, workspace: { cwd: string; home: string
35
36
  } else if (use.tool === 'PowerShell') {
36
37
  effects.shell = { line: textField(use, 'command'), commands: [], isFullyParsed: false }
37
38
  } else if (use.tool === 'Edit') {
38
- const path = resolve(textField(use, 'file_path'))
39
+ const path = absolute(textField(use, 'file_path'))
39
40
  effects.writes = [{ path, contents: once(() => editedContents(use, path, fs)) }]
40
41
  } else if (use.tool === 'Write') {
41
- const path = resolve(textField(use, 'file_path'))
42
+ const path = absolute(textField(use, 'file_path'))
42
43
  const content = textField(use, 'content')
43
44
  effects.writes = [{ path, contents: once(async () => contentsOf(content, await previousContentOf(path, fs))) }]
44
45
  } else if (use.tool === 'NotebookEdit') {
45
- const path = resolve(textField(use, 'notebook_path'))
46
+ const path = absolute(textField(use, 'notebook_path'))
46
47
  effects.writes = [{ path, contents: once(async () => contentsOf(undefined, await previousContentOf(path, fs))) }]
47
48
  } else if (use.tool === 'Read') {
48
49
  effects.reads = [access(textField(use, 'file_path'))]
49
50
  } else if (use.tool === 'Grep') {
50
51
  effects.reads = [access(optionalTextField(use, 'path') ?? '')]
51
52
  } else if (use.tool === 'Glob') {
52
- const folder = resolve(optionalTextField(use, 'path') ?? '')
53
- effects.reads = [{ path: resolve(textField(use, 'pattern'), folder), contents: undefined }]
53
+ const folder = absolute(optionalTextField(use, 'path') ?? '')
54
+ effects.reads = [{ path: absolute(textField(use, 'pattern'), folder), contents: undefined }]
54
55
  } else if (use.tool === 'WebFetch') {
55
56
  effects.urls = [textField(use, 'url')]
56
57
  } else if (use.tool === 'Agent') {
@@ -1,18 +1,15 @@
1
- import { nameOf } from './paths.js'
2
- import { parse, type GlobPattern, type ParseEntry } from '../vendor.js'
1
+ import { basename, parse, type GlobPattern, type ParseEntry } from '../vendor.js'
2
+
3
+ export type ShellCommand = { argv: [string, ...string[]]; folder: string }
3
4
 
4
5
  export type ParsedShell = {
5
- commands: [string, ...string[]][]
6
+ commands: ShellCommand[]
6
7
  writes: string[]
7
8
  reads: string[]
8
9
  fetches: string[]
9
10
  isFullyParsed: boolean
10
11
  }
11
12
 
12
- export type ShellCommand = { argv: [string, ...string[]]; folder: string }
13
-
14
- export type ParsedShellCommands = Omit<ParsedShell, 'commands'> & { commands: ShellCommand[] }
15
-
16
13
  type Word = { text: string; isGlob: boolean; isDynamic: boolean }
17
14
  type Marker = { raw: string; code: string[] }
18
15
  type Expansion = Marker & { end: number }
@@ -200,17 +197,12 @@ const inlineCodeFlags = new Map<string, readonly string[]>([
200
197
  ])
201
198
 
202
199
  export function parseShell(line: string): ParsedShell {
203
- const parsed = parseShellCommands(line)
204
- return { ...parsed, commands: parsed.commands.map((command) => command.argv) }
205
- }
206
-
207
- export function parseShellCommands(line: string): ParsedShellCommands {
208
- const result: ParsedShellCommands = { commands: [], writes: [], reads: [], fetches: [], isFullyParsed: true }
200
+ const result: ParsedShell = { commands: [], writes: [], reads: [], fetches: [], isFullyParsed: true }
209
201
  parseLine(line, '', result)
210
202
  return result
211
203
  }
212
204
 
213
- function parseLine(line: string, folder: string, result: ParsedShellCommands): void {
205
+ function parseLine(line: string, folder: string, result: ParsedShell): void {
214
206
  if (line.includes('\u0001')) {
215
207
  result.isFullyParsed = false
216
208
  return
@@ -475,7 +467,7 @@ function readHeredocBodies(source: string, from: number, heredocs: Heredoc[]): n
475
467
  return index
476
468
  }
477
469
 
478
- function walk(tokens: ParseEntry[], markers: Marker[], start: string, result: ParsedShellCommands): void {
470
+ function walk(tokens: ParseEntry[], markers: Marker[], start: string, result: ParsedShell): void {
479
471
  let folder = start
480
472
  const subshells: string[] = []
481
473
  let command = newCommand(false)
@@ -533,7 +525,7 @@ function isWordToken(token: ParseEntry): token is string | GlobPattern {
533
525
  return typeof token === 'string' || ('op' in token && token.op === 'glob')
534
526
  }
535
527
 
536
- function wordOf(token: string | GlobPattern, markers: Marker[], folder: string, result: ParsedShellCommands): Word {
528
+ function wordOf(token: string | GlobPattern, markers: Marker[], folder: string, result: ParsedShell): Word {
537
529
  const raw = typeof token === 'string' ? token : token.pattern
538
530
  for (const match of raw.matchAll(markerPattern)) {
539
531
  for (const code of markers[Number(match[1])]!.code) parseLine(code, folder, result)
@@ -541,11 +533,11 @@ function wordOf(token: string | GlobPattern, markers: Marker[], folder: string,
541
533
  return {
542
534
  text: raw.replace(markerPattern, (_, index: string) => markers[Number(index)]!.raw),
543
535
  isGlob: typeof token !== 'string',
544
- isDynamic: /[$\u0001]/.test(nameOf(raw)),
536
+ isDynamic: /[$\u0001]/.test(basename(raw)),
545
537
  }
546
538
  }
547
539
 
548
- function finish(command: Command, operator: string, folder: string, result: ParsedShellCommands): string {
540
+ function finish(command: Command, operator: string, folder: string, result: ParsedShell): string {
549
541
  for (const redirect of command.redirects) addRedirect(redirect, folder, result)
550
542
  const words = withoutPrefixes(command.words)
551
543
  const [first, ...args] = words
@@ -569,14 +561,14 @@ function withoutPrefixes(words: Word[]): Word[] {
569
561
  return words.slice(start)
570
562
  }
571
563
 
572
- function addRedirect({ operator, target }: Redirect, folder: string, result: ParsedShellCommands): void {
564
+ function addRedirect({ operator, target }: Redirect, folder: string, result: ParsedShell): void {
573
565
  if (operator === '<' || operator === '<>') addPath(result, 'reads', target, folder)
574
566
  if (writeRedirects.has(operator) || (operator === '>&' && !/^([0-9]+|-)$/.test(target.text))) addPath(result, 'writes', target, folder)
575
567
  }
576
568
 
577
- function addCommand(first: Word, args: Word[], folder: string, stdin: Stdin | undefined, result: ParsedShellCommands): string | undefined {
569
+ function addCommand(first: Word, args: Word[], folder: string, stdin: Stdin | undefined, result: ParsedShell): string | undefined {
578
570
  if (first.isDynamic || first.isGlob) result.isFullyParsed = false
579
- const program = nameOf(first.text)
571
+ const program = basename(first.text)
580
572
  if (program === 'git') addGit(first, args, folder, result)
581
573
  else if (program === 'find') addFind(first, args, folder, result)
582
574
  else if (wrappers.has(program)) addWrapper(first, program, args, folder, stdin, result)
@@ -589,7 +581,7 @@ function addCommand(first: Word, args: Word[], folder: string, stdin: Stdin | un
589
581
  return undefined
590
582
  }
591
583
 
592
- function addGit(first: Word, args: Word[], folder: string, result: ParsedShellCommands): void {
584
+ function addGit(first: Word, args: Word[], folder: string, result: ParsedShell): void {
593
585
  let index = 0
594
586
  let gitFolder = folder
595
587
  for (let text = args[0]?.text; text?.startsWith('-') === true; text = args[index]?.text) {
@@ -600,7 +592,7 @@ function addGit(first: Word, args: Word[], folder: string, result: ParsedShellCo
600
592
  result.commands.push({ argv: [first.text, ...expandFlags('git', args.slice(index))], folder: gitFolder })
601
593
  }
602
594
 
603
- function addFind(first: Word, args: Word[], folder: string, result: ParsedShellCommands): void {
595
+ function addFind(first: Word, args: Word[], folder: string, result: ParsedShell): void {
604
596
  const own: string[] = []
605
597
  const actions: Word[][] = []
606
598
  for (let index = 0; index < args.length; index += 1) {
@@ -628,7 +620,7 @@ function findStarts(args: Word[]): Word[] {
628
620
  return starts.length > 0 ? starts : [literal('.')]
629
621
  }
630
622
 
631
- function addWrapper(first: Word, program: string, args: Word[], folder: string, stdin: Stdin | undefined, result: ParsedShellCommands): void {
623
+ function addWrapper(first: Word, program: string, args: Word[], folder: string, stdin: Stdin | undefined, result: ParsedShell): void {
632
624
  const wrapper = wrappers.get(program)!
633
625
  if (program === 'xargs' && stdin?.kind !== 'text') result.isFullyParsed = false
634
626
  const { options, operands } = readArguments(args, wrapper, true)
@@ -651,7 +643,7 @@ function addWrapper(first: Word, program: string, args: Word[], folder: string,
651
643
  if (innerFirst !== undefined) addCommand(innerFirst, innerArgs, wrappedFolder, stdin, result)
652
644
  }
653
645
 
654
- function addEffects(program: string, args: Word[], flags: string[], folder: string, stdin: Stdin | undefined, result: ParsedShellCommands): void {
646
+ function addEffects(program: string, args: Word[], flags: string[], folder: string, stdin: Stdin | undefined, result: ParsedShell): void {
655
647
  const fileProgram = fileReaders.get(program)
656
648
  const fileWriter = fileWriters.get(program)
657
649
  const copyProgram = copyPrograms.get(program)
@@ -675,7 +667,7 @@ function addEffects(program: string, args: Word[], flags: string[], folder: stri
675
667
  }
676
668
  }
677
669
 
678
- function addShell(args: Word[], folder: string, stdin: Stdin | undefined, result: ParsedShellCommands): void {
670
+ function addShell(args: Word[], folder: string, stdin: Stdin | undefined, result: ParsedShell): void {
679
671
  const { options, operands } = readArguments(args, { valued: 'oO', longValued: ['--rcfile', '--init-file'] }, true)
680
672
  const script = operands[0]
681
673
  if (options.some((option) => option.name === '-c')) {
@@ -683,35 +675,35 @@ function addShell(args: Word[], folder: string, stdin: Stdin | undefined, result
683
675
  } else if (script === undefined || options.some((option) => option.name === '-s')) addStdinCode(stdin, folder, result)
684
676
  }
685
677
 
686
- function addSsh(args: Word[], stdin: Stdin | undefined, result: ParsedShellCommands): void {
678
+ function addSsh(args: Word[], stdin: Stdin | undefined, result: ParsedShell): void {
687
679
  const { operands } = readArguments(args, { valued: 'BbcDEeFIiJLlmOoPpQRSWw' }, true)
688
680
  const remote = operands.slice(1)
689
681
  if (remote.length > 0) parseLine(remote.map((word) => word.text).join(' '), '~', result)
690
682
  else if (operands.length > 0) addStdinCode(stdin, '~', result)
691
683
  }
692
684
 
693
- function addSu(args: Word[], folder: string, result: ParsedShellCommands): void {
685
+ function addSu(args: Word[], folder: string, result: ParsedShell): void {
694
686
  const code = readArguments(args, su, false).options.findLast((option) => suCodeOptions.includes(option.name))?.value
695
687
  if (code !== undefined) parseLine(code.text, folder, result)
696
688
  }
697
689
 
698
- function addFetches(fetcher: OptionSpec, args: Word[], result: ParsedShellCommands): void {
690
+ function addFetches(fetcher: OptionSpec, args: Word[], result: ParsedShell): void {
699
691
  const { options, operands } = readArguments(args, fetcher, false)
700
692
  const urls = [...options.filter((option) => option.name === '--url').flatMap((option) => (option.value === undefined ? [] : [option.value])), ...operands]
701
693
  for (const url of urls) result.fetches.push(/^[a-z][a-z0-9+.-]*:\/\//i.test(url.text) ? url.text : `http://${url.text}`)
702
694
  }
703
695
 
704
- function addStdinCode(stdin: Stdin | undefined, folder: string, result: ParsedShellCommands): void {
696
+ function addStdinCode(stdin: Stdin | undefined, folder: string, result: ParsedShell): void {
705
697
  if (stdin?.kind === 'text') parseLine(stdin.text, folder, result)
706
698
  if (stdin?.kind === 'pipe') result.isFullyParsed = false
707
699
  }
708
700
 
709
- function addCopy(program: CopyProgram, args: Word[], folder: string, result: ParsedShellCommands): void {
701
+ function addCopy(program: CopyProgram, args: Word[], folder: string, result: ParsedShell): void {
710
702
  const { options, operands } = readArguments(args, program, false)
711
703
  const target = options.findLast((option) => program.targetOptions?.includes(option.name) === true)?.value
712
704
  const only = operands[0]
713
705
  if (program.linksHere === true && target === undefined && operands.length === 1 && only !== undefined) {
714
- addPath(result, 'writes', { ...only, text: nameOf(only.text) }, folder)
706
+ addPath(result, 'writes', { ...only, text: basename(only.text) }, folder)
715
707
  return
716
708
  }
717
709
  const destination = target ?? operands.at(-1)
@@ -720,12 +712,12 @@ function addCopy(program: CopyProgram, args: Word[], folder: string, result: Par
720
712
  const isLocal = (word: Word) => program.skipsRemote !== true || !/^[^/]*:/.test(word.text)
721
713
  for (const source of sources.filter(isLocal)) {
722
714
  if (program.sources !== undefined) addPath(result, program.sources, source, folder)
723
- if (isLocal(destination) && !source.isGlob && !destination.isGlob) addPathText(result, 'writes', `${destination.text.replace(/\/+$/, '')}/${nameOf(source.text)}`, folder)
715
+ if (isLocal(destination) && !source.isGlob && !destination.isGlob) addPathText(result, 'writes', `${destination.text.replace(/\/+$/, '')}/${basename(source.text)}`, folder)
724
716
  }
725
717
  if (isLocal(destination)) addPath(result, 'writes', destination, folder)
726
718
  }
727
719
 
728
- function addFileArguments(program: FileProgram, args: Word[], folder: string, result: ParsedShellCommands): void {
720
+ function addFileArguments(program: FileProgram, args: Word[], folder: string, result: ParsedShell): void {
729
721
  const { options, operands } = readArguments(args, program, false)
730
722
  const inPlace = options.find((option) => program.inPlaceOptions?.includes(option.name) === true && (program !== awk || option.value?.text === 'inplace'))
731
723
  let files = operands
@@ -812,11 +804,11 @@ function changedFolder(args: Word[], folder: string): string {
812
804
  return joinFolder(folder, target.text === '-' ? '$OLDPWD' : target.text)
813
805
  }
814
806
 
815
- function addPath(result: ParsedShellCommands, access: Access, word: Word | undefined, folder: string): void {
807
+ function addPath(result: ParsedShell, access: Access, word: Word | undefined, folder: string): void {
816
808
  if (word !== undefined && !word.isGlob) addPathText(result, access, word.text, folder)
817
809
  }
818
810
 
819
- function addPathText(result: ParsedShellCommands, access: Access, path: string, folder: string): void {
811
+ function addPathText(result: ParsedShell, access: Access, path: string, folder: string): void {
820
812
  if (path === '' || path.startsWith('/dev/')) return
821
813
  const joined = joinFolder(folder, path)
822
814
  if (dynamicPattern.test(joined.replace(homePrefix, ''))) result.isFullyParsed = false
@@ -1,3 +1,5 @@
1
+ import { basename, dirname, resolve } from '../vendor.js'
2
+
1
3
  export type FileSystem = {
2
4
  read: (path: string) => Promise<string>
3
5
  stat: (path: string) => Promise<{ kind: 'file' | 'dir' | 'other'; realPath?: string }>
@@ -9,23 +11,6 @@ export function expandHome(path: string, home: string): string {
9
11
  return prefix === undefined ? path : `${home}${path.slice(prefix.length)}`
10
12
  }
11
13
 
12
- export function resolvePath(path: string, folder: string): string {
13
- const parts: string[] = []
14
- for (const part of (path.startsWith('/') ? path : `${folder}/${path}`).split('/')) {
15
- if (part === '..') parts.pop()
16
- else if (part !== '' && part !== '.') parts.push(part)
17
- }
18
- return `/${parts.join('/')}`
19
- }
20
-
21
- export function parentOf(path: string): string {
22
- return resolvePath('..', path)
23
- }
24
-
25
- export function nameOf(path: string): string {
26
- return path.slice(path.lastIndexOf('/') + 1)
27
- }
28
-
29
14
  export function relativePath(base: string, path: string): string | undefined {
30
15
  if (path === base) return ''
31
16
  const prefix = base === '/' ? '/' : `${base}/`
@@ -35,6 +20,6 @@ export function relativePath(base: string, path: string): string | undefined {
35
20
  export async function realPathOf(path: string, fs: FileSystem): Promise<string | undefined> {
36
21
  const stat = await fs.stat(path).catch(() => undefined)
37
22
  if (stat !== undefined || path === '/') return stat?.realPath
38
- const folder = await realPathOf(parentOf(path), fs)
39
- return folder === undefined ? undefined : resolvePath(nameOf(path), folder)
23
+ const folder = await realPathOf(dirname(path), fs)
24
+ return folder === undefined ? undefined : resolve(folder, basename(path))
40
25
  }
package/src/vendor.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { Validator } from '@cfworker/json-schema'
2
+ export { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve } from 'pathe'
2
3
  export { default as picomatch } from 'picomatch/posix'
3
4
  export { parse, quote, type GlobPattern, type ParseEntry } from 'shell-quote'
@@ -24,9 +24,14 @@ export type Fakes = {
24
24
  ui: {
25
25
  ask?: Claude['ui']['ask'];
26
26
  open?: Claude['ui']['open'];
27
+ scroll?: Claude['ui']['scroll'];
28
+ };
29
+ session: {
30
+ messages?: Claude['session']['messages'];
27
31
  };
28
32
  agent: {
29
33
  list?: Claude['agent']['list'];
34
+ spawn?: Claude['agent']['spawn'];
30
35
  };
31
36
  clock: {
32
37
  after?: Claude['clock']['after'];
@@ -1 +1 @@
1
- {"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAGlD,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IAC/E,EAAE,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;IAC7J,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IACzC,QAAQ,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;IAC/C,EAAE,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAC/F,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAA;KAAE,CAAA;IACrD,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IAC3E,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IAC7E,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;CACxC,CAAA;AAED,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAA;IACzC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB,CAAA;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAsI/F"}