@cmodjs/core 0.1.11 → 0.1.12

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 (62) hide show
  1. package/docs/commands.md +16 -13
  2. package/docs/dependencies.md +8 -0
  3. package/docs/hooks.md +10 -1
  4. package/docs/index.md +4 -3
  5. package/docs/install-steps.md +19 -3
  6. package/docs/jobs.md +23 -7
  7. package/docs/mod.md +58 -3
  8. package/docs/testing.md +26 -3
  9. package/jobs/context.d.ts +2 -2
  10. package/jobs/context.d.ts.map +1 -1
  11. package/jobs/context.js +2 -2
  12. package/jobs/permissions/decide-permission.d.ts +2 -2
  13. package/jobs/permissions/decide-permission.d.ts.map +1 -1
  14. package/jobs/permissions/match-target.d.ts +3 -2
  15. package/jobs/permissions/match-target.d.ts.map +1 -1
  16. package/jobs/permissions/match-target.js +24 -15
  17. package/jobs/permissions.d.ts +1 -1
  18. package/jobs/permissions.d.ts.map +1 -1
  19. package/jobs/permissions.js +18 -6
  20. package/jobs/prompt.d.ts.map +1 -1
  21. package/jobs/prompt.js +34 -3
  22. package/mod.d.ts +11 -2
  23. package/mod.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/records.d.ts +4 -0
  26. package/records.d.ts.map +1 -1
  27. package/records.js +27 -6
  28. package/register.d.ts.map +1 -1
  29. package/register.js +3 -0
  30. package/runtime/claude.d.ts +7 -0
  31. package/runtime/claude.d.ts.map +1 -1
  32. package/runtime/hooks.js +4 -4
  33. package/runtime/lifecycle.d.ts +1 -0
  34. package/runtime/lifecycle.d.ts.map +1 -1
  35. package/runtime/lifecycle.js +80 -10
  36. package/runtime/state.d.ts +2 -1
  37. package/runtime/state.d.ts.map +1 -1
  38. package/runtime/state.js +12 -1
  39. package/src/jobs/check.ts +2 -2
  40. package/src/jobs/context.ts +5 -5
  41. package/src/jobs/permissions/decide-permission.ts +4 -4
  42. package/src/jobs/permissions/match-target.ts +24 -16
  43. package/src/jobs/permissions.ts +18 -7
  44. package/src/jobs/prompt.ts +31 -3
  45. package/src/mod.ts +15 -1
  46. package/src/records.ts +28 -8
  47. package/src/register.ts +3 -0
  48. package/src/runtime/claude.ts +3 -0
  49. package/src/runtime/hooks.ts +5 -5
  50. package/src/runtime/lifecycle.ts +85 -10
  51. package/src/runtime/state.ts +13 -1
  52. package/src/testing/fake-claude.ts +19 -3
  53. package/src/testing.ts +1 -0
  54. package/src/utils/call-effects.ts +13 -11
  55. package/testing/fake-claude.d.ts +9 -0
  56. package/testing/fake-claude.d.ts.map +1 -1
  57. package/testing/fake-claude.js +13 -2
  58. package/testing.d.ts.map +1 -1
  59. package/testing.js +1 -0
  60. package/utils/call-effects.d.ts +4 -8
  61. package/utils/call-effects.d.ts.map +1 -1
  62. package/utils/call-effects.js +6 -3
@@ -4,7 +4,7 @@ import { configFolders } from '../records.js'
4
4
  import type { Claude } from './claude.js'
5
5
  import type { RoutedHook } from './router.js'
6
6
  import { reservedKeys, toolInputOf, type ToolCalls } from './tool-calls.js'
7
- import { callEffects, type FileAccess, type ToolUse } from '../utils/call-effects.js'
7
+ import { callEffects, inputOf, type CallBase, type FileAccess } from '../utils/call-effects.js'
8
8
  import { dynamicPattern } from '../utils/parse-shell.js'
9
9
  import { messageOf } from '../utils/text.js'
10
10
 
@@ -53,14 +53,14 @@ const readFields: Record<ModEvent, readonly string[]> = {
53
53
  }
54
54
 
55
55
  export function classicHook<E extends Exclude<ModEvent, 'PreToolUse'>>(name: string, event: E, hook: ModHook<E>, claude: Claude, calls: ToolCalls): RoutedHook<RoutedEvent> {
56
- const inputOf = async (e: unknown): Promise<unknown> => {
56
+ const hookInputOf = async (e: unknown): Promise<unknown> => {
57
57
  if (event !== 'PostToolUse' && event !== 'PostToolUseFailure') return e
58
58
  const input = e as ClassicHookInputs['PostToolUse' | 'PostToolUseFailure']
59
59
  const cwd = calls.cwdOf(input.tool_use_id) ?? input.cwd
60
- return { ...input, files: await callFiles(name, claude, { tool: input.tool_name, input: input.tool_input }, cwd) }
60
+ return { ...input, files: await callFiles(name, claude, { tool: input.tool_name, input: inputOf(input.tool_input) }, cwd) }
61
61
  }
62
62
  const resultOf = async (e: unknown): Promise<ClassicFields | undefined> => {
63
- const answer = await hook((await inputOf(e)) as HookInput<E>)
63
+ const answer = await hook((await hookInputOf(e)) as HookInput<E>)
64
64
  if (answer === undefined) return undefined
65
65
  if (answer.systemMessage !== undefined) claude.ui.log(answer.systemMessage)
66
66
  return classicResult(event, answer)
@@ -165,7 +165,7 @@ async function preToolUseInput(name: string, envelope: Frozen<Args<'tool.call'>>
165
165
  }
166
166
  }
167
167
 
168
- async function callFiles(name: string, claude: Claude, use: ToolUse, cwd: string): Promise<CallFiles> {
168
+ async function callFiles(name: string, claude: Claude, use: CallBase, cwd: string): Promise<CallFiles> {
169
169
  try {
170
170
  const home = await claude.env.home()
171
171
  if (home === undefined) throw new Error('HOME is not set, so ~ in a path has no meaning.')
@@ -19,6 +19,7 @@ export type Plugin = {
19
19
  readonly store: string
20
20
  readonly isInstalled: boolean
21
21
  readonly shouldRecord: boolean
22
+ readonly keys: Readonly<Record<string, string>>
22
23
  }
23
24
 
24
25
  export type Phase = 'starting' | 'installing' | 'waiting' | 'declined' | 'failed' | 'ready' | 'active'
@@ -36,6 +37,7 @@ type ModRuntime = {
36
37
  readonly router: Router
37
38
  readonly progress: Progress
38
39
  readonly dataFolder: string
40
+ readonly keys: Readonly<Record<string, string>>
39
41
  readonly checksPermissions: () => boolean
40
42
  }
41
43
 
@@ -74,12 +76,12 @@ export async function readPlugin(claude: Claude): Promise<Plugin> {
74
76
  const manifest = (await readJson(read, `${root}/.claude-plugin/plugin.json`)) as { version?: unknown } | undefined
75
77
  const version = typeof manifest?.version === 'string' ? manifest.version : undefined
76
78
  const store = storeFolder({ HOME: await claude.env.home(), XDG_DATA_HOME: await claude.env.dataHome() })
77
- const plugin = { name, root, version, store }
78
79
  if (name === cmodPluginName) {
79
80
  const installed = await cmodVersion(claude)
80
- return { ...plugin, isInstalled: version !== undefined && installed !== undefined && isAtLeast(installed, version), shouldRecord: false }
81
+ return { name, root, version, store, keys: {}, isInstalled: version !== undefined && installed !== undefined && isAtLeast(installed, version), shouldRecord: false }
81
82
  }
82
83
  const steps = readSteps(await readJson(read, `${root}/package.json`)) ?? {}
84
+ const plugin = { name, root, version, store, keys: steps.keys ?? {} }
83
85
  const record = await readRecord(read, store, name)
84
86
  if (Object.keys(steps).length === 0) return { ...plugin, isInstalled: true, shouldRecord: record?.version !== version }
85
87
  const scripts = await scriptsSha256(steps, {
@@ -181,7 +183,7 @@ export function createLifecycle<State extends object>(definition: ModDefinition<
181
183
  const activate = async () => {
182
184
  if (runtime === undefined || plugin === undefined) return
183
185
  const activeRouter = createRouter()
184
- const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name), checksPermissions }).catch((error: unknown) => {
186
+ const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name), keys: plugin.keys, checksPermissions }).catch((error: unknown) => {
185
187
  fail(messageOf(error), 'Fix it, then run /reload-plugins.')
186
188
  throw error
187
189
  })
@@ -215,8 +217,12 @@ export function createLifecycle<State extends object>(definition: ModDefinition<
215
217
  return
216
218
  }
217
219
  showLine().wait('Waiting for your answer')
218
- const removal = event.uninstall === '' ? '' : `, and ${event.uninstall} when you remove it`
219
- const answer = await claude().ui.ask(`${name} runs ${event.install} to install${removal}. Run it now?`, { options: ['Install', 'Not now'], header: 'Install' })
220
+ const actions = [
221
+ ...(event.install === '' ? [] : [`runs ${event.install} to install`]),
222
+ ...(event.uninstall === '' ? [] : [`runs ${event.uninstall} when you remove it`]),
223
+ ...(event.keys === '' ? [] : [`binds ${event.keys}`]),
224
+ ]
225
+ const answer = await claude().ui.ask(`${name} ${listed(actions)}. Install it now?`, { options: ['Install', 'Not now'], header: 'Install' })
220
226
  if (answer === 'Install') return install(event.sha256)
221
227
  phase = 'declined'
222
228
  endLine()
@@ -350,7 +356,8 @@ export function createLifecycle<State extends object>(definition: ModDefinition<
350
356
  }
351
357
 
352
358
  async function createMod<State extends object>(definition: ModDefinition<State>, runtime: ModRuntime): Promise<ActiveMod<State>> {
353
- const { claude, router, progress } = runtime
359
+ const { router, progress } = runtime
360
+ const claude = checkingKeys(runtime.claude, definition.name, runtime.keys)
354
361
  const added: string[] = []
355
362
  const hookEvents: ModEvent[] = []
356
363
  const announce = (feature: string) => {
@@ -367,12 +374,26 @@ async function createMod<State extends object>(definition: ModDefinition<State>,
367
374
  name: definition.name,
368
375
  state: modState.state,
369
376
  dataFolder: runtime.dataFolder,
370
- on(event, hook) {
377
+ on(event, hook, options) {
378
+ const bounded = options?.timeoutMs === undefined ? hook : timedHook(definition.name, event, hook, checkedMs(definition.name, `the ${event} hook's timeoutMs`, options.timeoutMs), claude)
371
379
  if (!hookEvents.includes(event)) hookEvents.push(event)
372
- if (event === 'PreToolUse') router.add('tool.call', preToolUseHook(definition.name, hook as ModHook<'PreToolUse'>, claude, agents, held))
373
- else router.add(`classic.${event as Exclude<ModEvent, 'PreToolUse'>}`, classicHook(definition.name, event as Exclude<ModEvent, 'PreToolUse'>, hook as ModHook<Exclude<ModEvent, 'PreToolUse'>>, claude, agents))
380
+ if (event === 'PreToolUse') router.add('tool.call', preToolUseHook(definition.name, bounded as ModHook<'PreToolUse'>, claude, agents, held))
381
+ else router.add(`classic.${event as Exclude<ModEvent, 'PreToolUse'>}`, classicHook(definition.name, event as Exclude<ModEvent, 'PreToolUse'>, bounded as ModHook<Exclude<ModEvent, 'PreToolUse'>>, claude, agents))
374
382
  },
375
383
  use: (job) => job({ mod, claude, on: (event, hook) => router.add(event, hook), announce, reserveName, toolCalls: agents }),
384
+ every(ms, hook) {
385
+ let isRunning = false
386
+ return claude.clock.every(checkedMs(definition.name, 'mod.every', ms), () => {
387
+ if (isRunning) return
388
+ isRunning = true
389
+ void Promise.resolve()
390
+ .then(hook)
391
+ .catch((error: unknown) => claude.ui.log(`${definition.name}: the every ${ms} ms hook failed: ${messageOf(error)}`))
392
+ .finally(() => {
393
+ isRunning = false
394
+ })
395
+ })
396
+ },
376
397
  ui: area.ui,
377
398
  process: {
378
399
  run: (argv, init) => claude.process.run(argv, init),
@@ -387,8 +408,19 @@ async function createMod<State extends object>(definition: ModDefinition<State>,
387
408
  },
388
409
  http: { fetch: (url, init) => claude.http.fetch(url, init) },
389
410
  settings: { read: (args) => claude.settings.read(args) },
390
- session: { messages: claude.session.messages },
411
+ session: {
412
+ messages: claude.session.messages,
413
+ async append(text) {
414
+ const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } })
415
+ if (added.deny !== undefined) throw new Error(`${definition.name}: Claude Code refused the note: ${added.deny}`)
416
+ },
417
+ async submit(text) {
418
+ const submitted = await claude.prompt.submit({ text })
419
+ if ('drop' in submitted && typeof submitted.drop === 'string') throw new Error(`${definition.name}: Claude Code dropped the prompt: ${submitted.drop}`)
420
+ },
421
+ },
391
422
  agent: { spawn: (args) => claude.agent.spawn(args) },
423
+ model: { complete: (request, options) => claude.model.complete(request, options) },
392
424
  get projectRoot() {
393
425
  return modState.root
394
426
  },
@@ -464,6 +496,49 @@ async function createMod<State extends object>(definition: ModDefinition<State>,
464
496
  return { mod, added }
465
497
  }
466
498
 
499
+ function checkingKeys(claude: Claude, name: string, keys: Readonly<Record<string, string>>): Claude {
500
+ const bound = Object.entries(keys)
501
+ if (bound.length === 0) return claude
502
+ const register: Claude['command']['register'] = (command) => {
503
+ for (const [key, commandName] of bound) {
504
+ if (commandName === command.name && command.immediate !== true) {
505
+ claude.ui.log(`${name}: ${key} runs /${command.name}, which waits for Claude's turn to end and adds a row to the conversation. Give /${command.name} immediate: true.`)
506
+ }
507
+ }
508
+ return claude.command.register(command)
509
+ }
510
+ return { ...claude, command: { ...claude.command, register } }
511
+ }
512
+
513
+ const longestTimerMs = 2_147_483_647
514
+
515
+ function checkedMs(name: string, subject: string, ms: number): number {
516
+ if (!Number.isInteger(ms) || ms <= 0 || ms > longestTimerMs) throw new Error(`${name}: ${subject} is ${ms}. Give a whole number of milliseconds above 0 and at most ${longestTimerMs}.`)
517
+ return ms
518
+ }
519
+
520
+ function timedHook<E extends ModEvent>(name: string, event: E, hook: ModHook<E>, ms: number, claude: Claude): ModHook<E> {
521
+ return (input) =>
522
+ new Promise((resolve, reject) => {
523
+ const timer = claude.clock.after(ms, () => {
524
+ claude.ui.log(`${name}: the ${event} hook passed its ${ms / 1000} s timeout`)
525
+ resolve(undefined)
526
+ })
527
+ Promise.resolve()
528
+ .then(() => hook(input))
529
+ .then(
530
+ (answer) => {
531
+ timer.cancel()
532
+ resolve(answer)
533
+ },
534
+ (error: unknown) => {
535
+ timer.cancel()
536
+ reject(error)
537
+ },
538
+ )
539
+ })
540
+ }
541
+
467
542
  async function failsAs(subject: string, step: () => unknown): Promise<void> {
468
543
  try {
469
544
  await step()
@@ -32,7 +32,19 @@ const savedLifetimes: readonly SavedLifetime[] = ['session', 'project', 'global'
32
32
 
33
33
  const keptPerValue = 20
34
34
 
35
- export function createState<State extends object>({ name, initial, session, root, claude, changed }: StateOptions): ModState<State> {
35
+ const listeners = new WeakMap<object, Set<() => void>>()
36
+
37
+ export function onStateChange(state: object, listener: () => void): void {
38
+ const own = listeners.get(state) ?? new Set()
39
+ own.add(listener)
40
+ listeners.set(state, own)
41
+ }
42
+
43
+ export function createState<State extends object>({ name, initial, session, root, claude, changed: redraw }: StateOptions): ModState<State> {
44
+ const changed = () => {
45
+ redraw()
46
+ for (const listener of listeners.get(state) ?? []) listener()
47
+ }
36
48
  const declared = declaredGroups(name, initial)
37
49
  const current = copied(declared)
38
50
  let defaults = declared
@@ -13,7 +13,9 @@ export type Fakes = {
13
13
  http: { fetch?: Claude['http']['fetch'] }
14
14
  settings: { read?: Claude['settings']['read'] }
15
15
  ui: { ask?: Claude['ui']['ask']; open?: Claude['ui']['open']; scroll?: Claude['ui']['scroll'] }
16
- session: { messages?: Claude['session']['messages'] }
16
+ session: { messages?: Claude['session']['messages']; append?: Claude['session']['append'] }
17
+ prompt: { submit?: Claude['prompt']['submit'] }
18
+ model: { complete?: Claude['model']['complete'] }
17
19
  agent: { list?: Claude['agent']['list']; spawn?: Claude['agent']['spawn'] }
18
20
  clock: { after?: Claude['clock']['after']; every?: Claude['clock']['every'] }
19
21
  cmod: { call?: Claude['cmod']['call'] }
@@ -21,6 +23,8 @@ export type Fakes = {
21
23
 
22
24
  export type Shown = {
23
25
  readonly toasts: string[]
26
+ readonly notes: string[]
27
+ readonly prompts: string[]
24
28
  readonly logs: string[]
25
29
  readonly debug: string[]
26
30
  readonly statuses: (string | undefined)[]
@@ -40,8 +44,8 @@ export type FakeClaude = {
40
44
 
41
45
  export function fakeClaude(plugin: { readonly name: string; readonly root: string }): FakeClaude {
42
46
  const calls: TestCall[] = []
43
- const fakes: Fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, session: {}, agent: {}, clock: {}, cmod: {} }
44
- const shown: Shown = { toasts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [] }
47
+ const fakes: Fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, session: {}, prompt: {}, model: {}, agent: {}, clock: {}, cmod: {} }
48
+ const shown: Shown = { toasts: [], notes: [], prompts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [] }
45
49
  const unplacedPanes = new Set<string>()
46
50
  let focusedPane: string | undefined
47
51
  const store = new Map<string, unknown>()
@@ -61,6 +65,15 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
61
65
  return async (...args: Args): Promise<Result> => run(...args)
62
66
  }
63
67
 
68
+ const appendNote: Claude['session']['append'] = async ({ message }) => {
69
+ shown.notes.push(message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join(''))
70
+ return { message, uuid: `note-${shown.notes.length}` } as unknown as Awaited<ReturnType<Claude['session']['append']>>
71
+ }
72
+ const submitPrompt: Claude['prompt']['submit'] = async ({ text }) => {
73
+ shown.prompts.push(text)
74
+ return { text, origin: { kind: 'plugin', name: plugin.name } } as unknown as Awaited<ReturnType<Claude['prompt']['submit']>>
75
+ }
76
+
64
77
  const claude: Claude = {
65
78
  plugin,
66
79
  ui: {
@@ -142,7 +155,10 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
142
155
  usage: async () => ({ context: { window: 200000 }, rateLimits: {}, cost: { usd: 0 } }) as unknown as Awaited<ReturnType<Claude['session']['usage']>>,
143
156
  surfaces: async () => ['terminal'],
144
157
  messages: rejected('session.messages', () => fakes.session.messages as ((args?: unknown) => Promise<unknown>) | undefined) as Claude['session']['messages'],
158
+ append: rejected('session.append', () => fakes.session.append ?? appendNote),
145
159
  },
160
+ prompt: { submit: rejected('prompt.submit', () => fakes.prompt.submit ?? submitPrompt) },
161
+ model: { complete: rejected('model.complete', () => fakes.model.complete) },
146
162
  command: {
147
163
  register: async (command) => {
148
164
  shown.commands.push(command.name)
package/src/testing.ts CHANGED
@@ -108,6 +108,7 @@ export function testMod<State extends object>(definition: ModDefinition<State>,
108
108
  store: storeFolder({ HOME: await claude.env.home(), XDG_DATA_HOME: await claude.env.dataHome() }),
109
109
  isInstalled: true,
110
110
  shouldRecord: false,
111
+ keys: {},
111
112
  }))
112
113
  await started
113
114
  if (lifecycle.phase !== 'active') throw lifecycle.failure ?? new Error(`${name} did not start: the lifecycle is ${lifecycle.phase}.`)
@@ -2,9 +2,7 @@ import { parseShell, type ShellCommand } from './parse-shell.js'
2
2
  import { expandHome, type FileSystem } from './paths.js'
3
3
  import { resolve } from '../vendor.js'
4
4
 
5
- export type ToolUse = { tool: string; input: unknown; agentId?: string; agentType?: string }
6
-
7
- export type CallBase = { tool: string; agentId?: string; agentType?: string }
5
+ export type CallBase = { tool: string; input: Readonly<Record<string, unknown>>; agentId?: string; agentType?: string }
8
6
  export type CommandCall = CallBase & { commands: [string, ...string[]][]; isFullyParsed: boolean }
9
7
  export type FileCall = CallBase & { path: string; content?: string; previousContent?: string }
10
8
  export type FetchCall = CallBase & { url: string }
@@ -20,7 +18,7 @@ export type CallEffects = {
20
18
  subagent: string | undefined
21
19
  }
22
20
 
23
- export function callEffects(use: ToolUse, workspace: { cwd: string; home: string; fs: FileSystem }): CallEffects {
21
+ export function callEffects(use: CallBase, workspace: { cwd: string; home: string; fs: FileSystem }): CallEffects {
24
22
  const { fs } = workspace
25
23
  const absolute = (path: string, folder = workspace.cwd) => resolve(folder, expandHome(path, workspace.home))
26
24
  const access = (path: string): FileAccess => ({ path: absolute(path), contents: undefined })
@@ -60,11 +58,15 @@ export function callEffects(use: ToolUse, workspace: { cwd: string; home: string
60
58
  return effects
61
59
  }
62
60
 
63
- export function callBaseOf({ tool, agentId, agentType }: ToolUse): CallBase {
64
- return { tool, ...(agentId === undefined ? {} : { agentId }), ...(agentType === undefined ? {} : { agentType }) }
61
+ export function inputOf(value: unknown): Readonly<Record<string, unknown>> {
62
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : {}
63
+ }
64
+
65
+ export function callBaseOf({ tool, input, agentId, agentType }: CallBase): CallBase {
66
+ return { tool, input, ...(agentId === undefined ? {} : { agentId }), ...(agentType === undefined ? {} : { agentType }) }
65
67
  }
66
68
 
67
- async function editedContents(use: ToolUse, path: string, fs: FileSystem): Promise<Contents> {
69
+ async function editedContents(use: CallBase, path: string, fs: FileSystem): Promise<Contents> {
68
70
  const previousContent = await previousContentOf(path, fs)
69
71
  const oldString = textField(use, 'old_string')
70
72
  const newString = textField(use, 'new_string')
@@ -90,16 +92,16 @@ function once<Value>(load: () => Promise<Value>): () => Promise<Value> {
90
92
  return () => (loaded ??= load())
91
93
  }
92
94
 
93
- function fieldOf(use: ToolUse, name: string): unknown {
94
- return typeof use.input === 'object' && use.input !== null ? (use.input as Record<string, unknown>)[name] : undefined
95
+ function fieldOf(use: CallBase, name: string): unknown {
96
+ return use.input[name]
95
97
  }
96
98
 
97
- function textField(use: ToolUse, name: string): string {
99
+ function textField(use: CallBase, name: string): string {
98
100
  const value = fieldOf(use, name)
99
101
  if (typeof value !== 'string') throw new Error(`The ${use.tool} call has no text field ${name}.`)
100
102
  return value
101
103
  }
102
104
 
103
- function optionalTextField(use: ToolUse, name: string): string | undefined {
105
+ function optionalTextField(use: CallBase, name: string): string | undefined {
104
106
  return fieldOf(use, name) === undefined ? undefined : textField(use, name)
105
107
  }
@@ -28,6 +28,13 @@ export type Fakes = {
28
28
  };
29
29
  session: {
30
30
  messages?: Claude['session']['messages'];
31
+ append?: Claude['session']['append'];
32
+ };
33
+ prompt: {
34
+ submit?: Claude['prompt']['submit'];
35
+ };
36
+ model: {
37
+ complete?: Claude['model']['complete'];
31
38
  };
32
39
  agent: {
33
40
  list?: Claude['agent']['list'];
@@ -43,6 +50,8 @@ export type Fakes = {
43
50
  };
44
51
  export type Shown = {
45
52
  readonly toasts: string[];
53
+ readonly notes: string[];
54
+ readonly prompts: string[];
46
55
  readonly logs: string[];
47
56
  readonly debug: string[];
48
57
  readonly statuses: (string | undefined)[];
@@ -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":"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"}
@@ -1,8 +1,8 @@
1
1
  import { elements } from './fake-elements.js';
2
2
  export function fakeClaude(plugin) {
3
3
  const calls = [];
4
- const fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, session: {}, agent: {}, clock: {}, cmod: {} };
5
- const shown = { toasts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [] };
4
+ const fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, session: {}, prompt: {}, model: {}, agent: {}, clock: {}, cmod: {} };
5
+ const shown = { toasts: [], notes: [], prompts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [] };
6
6
  const unplacedPanes = new Set();
7
7
  let focusedPane;
8
8
  const store = new Map();
@@ -20,6 +20,14 @@ export function fakeClaude(plugin) {
20
20
  const run = faked(call, answer);
21
21
  return async (...args) => run(...args);
22
22
  };
23
+ const appendNote = async ({ message }) => {
24
+ shown.notes.push(message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join(''));
25
+ return { message, uuid: `note-${shown.notes.length}` };
26
+ };
27
+ const submitPrompt = async ({ text }) => {
28
+ shown.prompts.push(text);
29
+ return { text, origin: { kind: 'plugin', name: plugin.name } };
30
+ };
23
31
  const claude = {
24
32
  plugin,
25
33
  ui: {
@@ -104,7 +112,10 @@ export function fakeClaude(plugin) {
104
112
  usage: async () => ({ context: { window: 200000 }, rateLimits: {}, cost: { usd: 0 } }),
105
113
  surfaces: async () => ['terminal'],
106
114
  messages: rejected('session.messages', () => fakes.session.messages),
115
+ append: rejected('session.append', () => fakes.session.append ?? appendNote),
107
116
  },
117
+ prompt: { submit: rejected('prompt.submit', () => fakes.prompt.submit ?? submitPrompt) },
118
+ model: { complete: rejected('model.complete', () => fakes.model.complete) },
108
119
  command: {
109
120
  register: async (command) => {
110
121
  shown.commands.push(command.name);
package/testing.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
package/testing.js CHANGED
@@ -46,6 +46,7 @@ export function testMod(definition, options = {}) {
46
46
  store: storeFolder({ HOME: await claude.env.home(), XDG_DATA_HOME: await claude.env.dataHome() }),
47
47
  isInstalled: true,
48
48
  shouldRecord: false,
49
+ keys: {},
49
50
  }));
50
51
  await started;
51
52
  if (lifecycle.phase !== 'active')
@@ -1,13 +1,8 @@
1
1
  import { type ShellCommand } from './parse-shell.js';
2
2
  import { type FileSystem } from './paths.js';
3
- export type ToolUse = {
4
- tool: string;
5
- input: unknown;
6
- agentId?: string;
7
- agentType?: string;
8
- };
9
3
  export type CallBase = {
10
4
  tool: string;
5
+ input: Readonly<Record<string, unknown>>;
11
6
  agentId?: string;
12
7
  agentType?: string;
13
8
  };
@@ -43,10 +38,11 @@ export type CallEffects = {
43
38
  urls: string[];
44
39
  subagent: string | undefined;
45
40
  };
46
- export declare function callEffects(use: ToolUse, workspace: {
41
+ export declare function callEffects(use: CallBase, workspace: {
47
42
  cwd: string;
48
43
  home: string;
49
44
  fs: FileSystem;
50
45
  }): CallEffects;
51
- export declare function callBaseOf({ tool, agentId, agentType }: ToolUse): CallBase;
46
+ export declare function inputOf(value: unknown): Readonly<Record<string, unknown>>;
47
+ export declare function callBaseOf({ tool, input, agentId, agentType }: CallBase): CallBase;
52
48
  //# sourceMappingURL=call-effects.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"call-effects.d.ts","sourceRoot":"","sources":["../src/utils/call-effects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAGxD,MAAM,MAAM,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5F,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC7E,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG;IAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAAA;AAClG,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC9F,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAClD,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEpE,MAAM,MAAM,QAAQ,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACrE,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAA;CAAE,CAAA;AAC1F,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAA;IACrF,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B,CAAA;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,GAAG,WAAW,CAsC/G;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,QAAQ,CAE1E"}
1
+ {"version":3,"file":"call-effects.d.ts","sourceRoot":"","sources":["../src/utils/call-effects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAGxD,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACvH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG;IAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAAA;AAClG,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC9F,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAClD,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEpE,MAAM,MAAM,QAAQ,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AACrE,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAA;CAAE,CAAA;AAC1F,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAA;IACrF,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B,CAAA;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,GAAG,WAAW,CAsChH;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEzE;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAG,QAAQ,CAElF"}
@@ -49,8 +49,11 @@ export function callEffects(use, workspace) {
49
49
  }
50
50
  return effects;
51
51
  }
52
- export function callBaseOf({ tool, agentId, agentType }) {
53
- return { tool, ...(agentId === undefined ? {} : { agentId }), ...(agentType === undefined ? {} : { agentType }) };
52
+ export function inputOf(value) {
53
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {};
54
+ }
55
+ export function callBaseOf({ tool, input, agentId, agentType }) {
56
+ return { tool, input, ...(agentId === undefined ? {} : { agentId }), ...(agentType === undefined ? {} : { agentType }) };
54
57
  }
55
58
  async function editedContents(use, path, fs) {
56
59
  const previousContent = await previousContentOf(path, fs);
@@ -77,7 +80,7 @@ function once(load) {
77
80
  return () => (loaded ??= load());
78
81
  }
79
82
  function fieldOf(use, name) {
80
- return typeof use.input === 'object' && use.input !== null ? use.input[name] : undefined;
83
+ return use.input[name];
81
84
  }
82
85
  function textField(use, name) {
83
86
  const value = fieldOf(use, name);