Call another mod
A mod offers methods to other mods in api. Another mod lists it as a dependency and calls those methods through mod.dependencies, typed from the provider's own types file.
Offer methods: api
Each method of api gets the caller's input and the provider's own mod, and returns its result or a promise of it. Inputs and results cross from one plugin to another, so keep them plain JSON data.
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
export const tracer = defineMod({
name: 'tracer',
api: {
signatures: async ({ path }: { path: string }, mod) =>
(await mod.fs.read(path)).split('\n').flatMap((text, index) => {
const name = /^export function (\w+)/.exec(text)?.[1]
return name === undefined ? [] : [{ name, line: index + 1 }]
}),
},
setup() {},
})Type the methods: CmodDependencies
The provider types its api in types/index.d.ts, and its .claude-plugin/plugin.json names that file as "types": "./types/index.d.ts". The file adds the mod to CmodDependencies, which the Claude Mod Manager (cmod) plugin declares on the claude-code module:
export type TracerSignature = { name: string; line: number }
export type Tracer = {
signatures(input: { path: string }): Promise<TracerSignature[]>
}
declare module 'claude-code' {
interface CmodDependencies {
tracer: Tracer
}
}- Each method takes one input and returns a promise.
- Once the name is in
CmodDependencies,tscchecks the provider's ownapiagainst it, so a method that returns another shape failscmod check. - A types file augments only
'claude-code'. Claude Code refuses a types file that augments any other module.
Call the methods: mod.dependencies
A mod that calls tracer lists it in its own .claude-plugin/plugin.json, and Claude Code installs it with the mod:
"dependencies": ["cmod", "tracer"]Claude Code then lays tracer's types file beside the mod's own types, so the call is typed:
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
import { slashCommand } from '../node_modules/@cmodjs/core/jobs/slash-command.js'
export const outline = defineMod({
name: 'outline',
setup(mod) {
mod.use(
slashCommand({
name: 'outline',
description: 'List the functions a file exports',
reply: async ({ args }, mod) => {
const signatures = await mod.dependencies.tracer.signatures({ path: args })
return signatures.map(({ name, line }) => `${name}:${line}`).join('\n')
},
}),
)
},
})When a call fails
A call that cannot answer rejects with one of these messages:
tracer is not installed. Run cmod install tracer.
tracer is installing. Try again when it's ready.
tracer has no method signatures.
tracer: <the message of the error the method threw>tracer is not installedcovers a provider whose code did not load, one the person declined to install, and one whose install failed.tracer is installingcovers a provider that is still running its install step.- When tracer is disabled, Claude Code unloads the mod that lists it, with its own error:
Dependency "tracer" is disabled — enable it or remove the dependency. - A call runs inside the deadline of the job that makes it (jobs.md), and 30 seconds anywhere else. Past the deadline of a job, the call fails with
mod.dependencies.tracer.signatures passed the 30 s deadline of slashCommand, naming the job. Anywhere else it fails withmod.dependencies.tracer.signatures passed its 30 s deadline.
notInstalled(name: string): stringnotInstalled, exported from mod.js, returns the first message for a mod name: <name> is not installed. Run cmod install <name>. A test uses it to expect that message.
Test the calls
testMod(outline, { dependencies: { tracer: { signatures: async () => [] } } }) answers tracer's methods with the functions given. A call to a mod the test does not fake fails with tracer is not installed. Run cmod install tracer. testing.md covers it.