@cmodjs/core 0.1.10 → 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.
- package/docs/commands.md +16 -13
- package/docs/dependencies.md +8 -0
- package/docs/hooks.md +10 -1
- package/docs/index.md +4 -3
- package/docs/install-steps.md +19 -3
- package/docs/jobs.md +23 -7
- package/docs/mod.md +58 -3
- package/docs/testing.md +27 -3
- package/jobs/context.d.ts +2 -2
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +2 -2
- package/jobs/permissions/decide-permission.d.ts +2 -2
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/match-target.d.ts +3 -2
- package/jobs/permissions/match-target.d.ts.map +1 -1
- package/jobs/permissions/match-target.js +24 -15
- package/jobs/permissions.d.ts +1 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +18 -6
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +34 -3
- package/mod.d.ts +11 -2
- package/mod.d.ts.map +1 -1
- package/package.json +1 -1
- package/records.d.ts +4 -0
- package/records.d.ts.map +1 -1
- package/records.js +27 -6
- package/register.d.ts.map +1 -1
- package/register.js +3 -0
- package/runtime/claude.d.ts +7 -0
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/hooks.js +4 -4
- package/runtime/lifecycle.d.ts +1 -0
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +80 -10
- package/runtime/state.d.ts +2 -1
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +12 -1
- package/src/jobs/check.ts +2 -2
- package/src/jobs/context.ts +5 -5
- package/src/jobs/permissions/decide-permission.ts +4 -4
- package/src/jobs/permissions/match-target.ts +24 -16
- package/src/jobs/permissions.ts +18 -7
- package/src/jobs/prompt.ts +31 -3
- package/src/mod.ts +15 -1
- package/src/records.ts +28 -8
- package/src/register.ts +3 -0
- package/src/runtime/claude.ts +3 -0
- package/src/runtime/hooks.ts +5 -5
- package/src/runtime/lifecycle.ts +85 -10
- package/src/runtime/state.ts +13 -1
- package/src/testing/fake-claude.ts +19 -3
- package/src/testing/fake-elements.ts +6 -1
- package/src/testing.ts +1 -0
- package/src/utils/call-effects.ts +13 -11
- package/testing/fake-claude.d.ts +9 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +13 -2
- package/testing/fake-elements.js +6 -1
- package/testing.d.ts.map +1 -1
- package/testing.js +1 -0
- package/utils/call-effects.d.ts +4 -8
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +6 -3
package/src/runtime/hooks.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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:
|
|
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.')
|
package/src/runtime/lifecycle.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
219
|
-
|
|
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 {
|
|
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,
|
|
373
|
-
else router.add(`classic.${event as Exclude<ModEvent, 'PreToolUse'>}`, classicHook(definition.name, event as Exclude<ModEvent, 'PreToolUse'>,
|
|
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: {
|
|
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()
|
package/src/runtime/state.ts
CHANGED
|
@@ -32,7 +32,19 @@ const savedLifetimes: readonly SavedLifetime[] = ['session', 'project', 'global'
|
|
|
32
32
|
|
|
33
33
|
const keptPerValue = 20
|
|
34
34
|
|
|
35
|
-
|
|
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)
|
|
@@ -53,7 +53,7 @@ export function textOf(node: RenderNode): string {
|
|
|
53
53
|
const { type, props, children = [] } = insideOf(node)
|
|
54
54
|
const inner = children.map(textOf).join('')
|
|
55
55
|
if (type === 'Box') return rowsOf(node, Number.POSITIVE_INFINITY).join('\n')
|
|
56
|
-
if (type === 'Button') return String(props['label'] ?? inner)
|
|
56
|
+
if (type === 'Button') return buttonText(String(props['label'] ?? inner), props)
|
|
57
57
|
if (type === 'Link') return inner === '' ? String(props['label'] ?? props['href']) : inner
|
|
58
58
|
if (type === 'Code') return String(props['source'])
|
|
59
59
|
if (type === 'Markdown') return String(props['text'])
|
|
@@ -67,6 +67,11 @@ export function textOf(node: RenderNode): string {
|
|
|
67
67
|
return inner
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function buttonText(label: string, props: Drawn['props']): string {
|
|
71
|
+
if (props['plain'] !== true) return `[ ${label} ]`
|
|
72
|
+
return props['hotkey'] === undefined ? label : `${String(props['hotkey'])}: ${label}`
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
function insideOf(node: RenderElement): Drawn {
|
|
71
76
|
const drawn = boxInsides.get(node) ?? (node as unknown as Partial<Drawn>)
|
|
72
77
|
return { type: String(drawn.type), props: drawn.props ?? {}, ...(drawn.children === undefined ? {} : { children: drawn.children }) }
|
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
|
|
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:
|
|
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
|
|
64
|
-
return
|
|
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:
|
|
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:
|
|
94
|
-
return
|
|
95
|
+
function fieldOf(use: CallBase, name: string): unknown {
|
|
96
|
+
return use.input[name]
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
function textField(use:
|
|
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:
|
|
105
|
+
function optionalTextField(use: CallBase, name: string): string | undefined {
|
|
104
106
|
return fieldOf(use, name) === undefined ? undefined : textField(use, name)
|
|
105
107
|
}
|
package/testing/fake-claude.d.ts
CHANGED
|
@@ -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":"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;
|
|
1
|
+
{"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"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"}
|
package/testing/fake-claude.js
CHANGED
|
@@ -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/fake-elements.js
CHANGED
|
@@ -52,7 +52,7 @@ export function textOf(node) {
|
|
|
52
52
|
if (type === 'Box')
|
|
53
53
|
return rowsOf(node, Number.POSITIVE_INFINITY).join('\n');
|
|
54
54
|
if (type === 'Button')
|
|
55
|
-
return String(props['label'] ?? inner);
|
|
55
|
+
return buttonText(String(props['label'] ?? inner), props);
|
|
56
56
|
if (type === 'Link')
|
|
57
57
|
return inner === '' ? String(props['label'] ?? props['href']) : inner;
|
|
58
58
|
if (type === 'Code')
|
|
@@ -70,6 +70,11 @@ export function textOf(node) {
|
|
|
70
70
|
}
|
|
71
71
|
return inner;
|
|
72
72
|
}
|
|
73
|
+
function buttonText(label, props) {
|
|
74
|
+
if (props['plain'] !== true)
|
|
75
|
+
return `[ ${label} ]`;
|
|
76
|
+
return props['hotkey'] === undefined ? label : `${String(props['hotkey'])}: ${label}`;
|
|
77
|
+
}
|
|
73
78
|
function insideOf(node) {
|
|
74
79
|
const drawn = boxInsides.get(node) ?? node;
|
|
75
80
|
return { type: String(drawn.type), props: drawn.props ?? {}, ...(drawn.children === undefined ? {} : { children: drawn.children }) };
|
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')
|
package/utils/call-effects.d.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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,
|
|
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"}
|
package/utils/call-effects.js
CHANGED
|
@@ -49,8 +49,11 @@ export function callEffects(use, workspace) {
|
|
|
49
49
|
}
|
|
50
50
|
return effects;
|
|
51
51
|
}
|
|
52
|
-
export function
|
|
53
|
-
return
|
|
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
|
|
83
|
+
return use.input[name];
|
|
81
84
|
}
|
|
82
85
|
function textField(use, name) {
|
|
83
86
|
const value = fieldOf(use, name);
|