[{"data":1,"prerenderedAt":1907},["ShallowReactive",2],{"docs-\u002Fdocs\u002Fv2\u002Fmodules\u002Fbuilding":3},{"id":4,"title":5,"body":6,"description":1900,"extension":1901,"meta":1902,"navigation":165,"path":1903,"seo":1904,"stem":1905,"__hash__":1906},"docs\u002Fdocs\u002Fv2\u002Fmodules\u002Fbuilding.md","Building a Module",{"type":7,"value":8,"toc":1889},"minimark",[9,13,22,108,113,116,782,784,794,895,902,906,915,918,1054,1064,1068,1073,1099,1259,1268,1272,1290,1457,1468,1472,1479,1749,1752,1756,1763,1825,1828,1832,1841,1845,1848,1878,1885],[10,11,5],"h1",{"id":12},"building-a-module",[14,15,16,17,21],"p",{},"A module starts as a folder with a ",[18,19,20],"code",{},"module.config.ts",". Everything else is optional\ndepending on what the module contributes.",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-bash shiki shiki-themes dark-plus","my-module\u002F\n├── server\u002F\n│   └── api\u002F\n├── src\u002F\n│   ├── prompts\u002F\n│   └── tools\u002F\n├── module.config.ts\n└── package.json\n","bash","",[18,30,31,40,50,62,70,81,91,99],{"__ignoreMap":28},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sCudf","my-module\u002F\n",[32,41,43,46],{"class":34,"line":42},2,[32,44,45],{"class":38},"├──",[32,47,49],{"class":48},"sKc5r"," server\u002F\n",[32,51,53,56,59],{"class":34,"line":52},3,[32,54,55],{"class":38},"│",[32,57,58],{"class":48},"   └──",[32,60,61],{"class":48}," api\u002F\n",[32,63,65,67],{"class":34,"line":64},4,[32,66,45],{"class":38},[32,68,69],{"class":48}," src\u002F\n",[32,71,73,75,78],{"class":34,"line":72},5,[32,74,55],{"class":38},[32,76,77],{"class":48},"   ├──",[32,79,80],{"class":48}," prompts\u002F\n",[32,82,84,86,88],{"class":34,"line":83},6,[32,85,55],{"class":38},[32,87,58],{"class":48},[32,89,90],{"class":48}," tools\u002F\n",[32,92,94,96],{"class":34,"line":93},7,[32,95,45],{"class":38},[32,97,98],{"class":48}," module.config.ts\n",[32,100,102,105],{"class":34,"line":101},8,[32,103,104],{"class":38},"└──",[32,106,107],{"class":48}," package.json\n",[109,110,112],"h2",{"id":111},"start-with-moduleconfigts","Start with module.config.ts",[14,114,115],{},"The config declares everything about the module — its identity, what env keys it\nneeds, what hooks it emits, and its boot lifecycle.",[23,117,121],{"className":118,"code":119,"language":120,"meta":28,"style":28},"language-ts shiki shiki-themes dark-plus","export default defineModule({\n    name: \"discord\",\n    description: \"Discord bot integration — receive messages and send replies.\",\n\n    env: {\n        DISCORD_BOT_TOKEN: {\n            required: true,\n            description: \"Bot token from the Discord developer portal.\",\n        },\n    },\n\n    options: {\n        mentionOnly: { type: \"boolean\", default: false },\n        channelIds:  { type: \"string\",  required: false },\n        prefix:      { type: \"string\",  default: \"\" },\n    },\n\n    emits: {\n        \"discord:message.received\": {} as {\n            content: string\n            username: string\n            channelId: string\n            reply: (text: string) => Promise\u003Cvoid>\n        },\n    },\n\n    async setup({ axon, options }) {\n        const token = axon.env.require(\"DISCORD_BOT_TOKEN\")\n        const client = new Client({ intents: [...] })\n\n        client.on(Events.MessageCreate, async (msg) => {\n            if (options.mentionOnly && !msg.mentions.has(client.user!)) return\n\n            await axon.callHook(\"discord:message.received\", {\n                content: msg.content,\n                username: msg.author.username,\n                channelId: msg.channelId,\n                reply: (text) => msg.reply(text),\n            })\n        })\n\n        await client.login(token)\n\n        axon.hook(\"axon:agent:shutdown\", () => client.destroy())\n    },\n})\n","ts",[18,122,123,139,151,161,167,175,182,193,203,209,215,220,228,255,279,301,306,311,319,336,349,359,369,404,409,414,419,442,477,501,506,544,590,595,617,633,653,668,698,704,710,715,735,740,771,776],{"__ignoreMap":28},[32,124,125,129,132,135],{"class":34,"line":35},[32,126,128],{"class":127},"sYbnZ","export",[32,130,131],{"class":127}," default",[32,133,134],{"class":38}," defineModule",[32,136,138],{"class":137},"sTNBD","({\n",[32,140,141,145,148],{"class":34,"line":42},[32,142,144],{"class":143},"s3F5K","    name:",[32,146,147],{"class":48}," \"discord\"",[32,149,150],{"class":137},",\n",[32,152,153,156,159],{"class":34,"line":52},[32,154,155],{"class":143},"    description:",[32,157,158],{"class":48}," \"Discord bot integration — receive messages and send replies.\"",[32,160,150],{"class":137},[32,162,163],{"class":34,"line":64},[32,164,166],{"emptyLinePlaceholder":165},true,"\n",[32,168,169,172],{"class":34,"line":72},[32,170,171],{"class":143},"    env:",[32,173,174],{"class":137}," {\n",[32,176,177,180],{"class":34,"line":83},[32,178,179],{"class":143},"        DISCORD_BOT_TOKEN:",[32,181,174],{"class":137},[32,183,184,187,191],{"class":34,"line":93},[32,185,186],{"class":143},"            required:",[32,188,190],{"class":189},"scz_3"," true",[32,192,150],{"class":137},[32,194,195,198,201],{"class":34,"line":101},[32,196,197],{"class":143},"            description:",[32,199,200],{"class":48}," \"Bot token from the Discord developer portal.\"",[32,202,150],{"class":137},[32,204,206],{"class":34,"line":205},9,[32,207,208],{"class":137},"        },\n",[32,210,212],{"class":34,"line":211},10,[32,213,214],{"class":137},"    },\n",[32,216,218],{"class":34,"line":217},11,[32,219,166],{"emptyLinePlaceholder":165},[32,221,223,226],{"class":34,"line":222},12,[32,224,225],{"class":143},"    options:",[32,227,174],{"class":137},[32,229,231,234,237,240,243,246,249,252],{"class":34,"line":230},13,[32,232,233],{"class":143},"        mentionOnly:",[32,235,236],{"class":137}," { ",[32,238,239],{"class":143},"type:",[32,241,242],{"class":48}," \"boolean\"",[32,244,245],{"class":137},", ",[32,247,248],{"class":143},"default:",[32,250,251],{"class":189}," false",[32,253,254],{"class":137}," },\n",[32,256,258,261,264,266,269,272,275,277],{"class":34,"line":257},14,[32,259,260],{"class":143},"        channelIds:",[32,262,263],{"class":137},"  { ",[32,265,239],{"class":143},[32,267,268],{"class":48}," \"string\"",[32,270,271],{"class":137},",  ",[32,273,274],{"class":143},"required:",[32,276,251],{"class":189},[32,278,254],{"class":137},[32,280,282,285,288,290,292,294,296,299],{"class":34,"line":281},15,[32,283,284],{"class":143},"        prefix:",[32,286,287],{"class":137},"      { ",[32,289,239],{"class":143},[32,291,268],{"class":48},[32,293,271],{"class":137},[32,295,248],{"class":143},[32,297,298],{"class":48}," \"\"",[32,300,254],{"class":137},[32,302,304],{"class":34,"line":303},16,[32,305,214],{"class":137},[32,307,309],{"class":34,"line":308},17,[32,310,166],{"emptyLinePlaceholder":165},[32,312,314,317],{"class":34,"line":313},18,[32,315,316],{"class":143},"    emits:",[32,318,174],{"class":137},[32,320,322,325,328,331,334],{"class":34,"line":321},19,[32,323,324],{"class":48},"        \"discord:message.received\"",[32,326,327],{"class":143},":",[32,329,330],{"class":137}," {} ",[32,332,333],{"class":127},"as",[32,335,174],{"class":137},[32,337,339,342,345],{"class":34,"line":338},20,[32,340,341],{"class":143},"            content",[32,343,344],{"class":137},": ",[32,346,348],{"class":347},"sNl3T","string\n",[32,350,352,355,357],{"class":34,"line":351},21,[32,353,354],{"class":143},"            username",[32,356,344],{"class":137},[32,358,348],{"class":347},[32,360,362,365,367],{"class":34,"line":361},22,[32,363,364],{"class":143},"            channelId",[32,366,344],{"class":137},[32,368,348],{"class":347},[32,370,372,375,378,381,383,386,389,392,395,398,401],{"class":34,"line":371},23,[32,373,374],{"class":38},"            reply",[32,376,377],{"class":137},": (",[32,379,380],{"class":143},"text",[32,382,344],{"class":137},[32,384,385],{"class":347},"string",[32,387,388],{"class":137},") ",[32,390,391],{"class":189},"=>",[32,393,394],{"class":347}," Promise",[32,396,397],{"class":137},"\u003C",[32,399,400],{"class":347},"void",[32,402,403],{"class":137},">\n",[32,405,407],{"class":34,"line":406},24,[32,408,208],{"class":137},[32,410,412],{"class":34,"line":411},25,[32,413,214],{"class":137},[32,415,417],{"class":34,"line":416},26,[32,418,166],{"emptyLinePlaceholder":165},[32,420,422,425,428,431,434,436,439],{"class":34,"line":421},27,[32,423,424],{"class":189},"    async",[32,426,427],{"class":38}," setup",[32,429,430],{"class":137},"({ ",[32,432,433],{"class":143},"axon",[32,435,245],{"class":137},[32,437,438],{"class":143},"options",[32,440,441],{"class":137}," }) {\n",[32,443,445,448,452,455,457,460,463,465,468,471,474],{"class":34,"line":444},28,[32,446,447],{"class":189},"        const",[32,449,451],{"class":450},"s9McN"," token",[32,453,454],{"class":137}," = ",[32,456,433],{"class":143},[32,458,459],{"class":137},".",[32,461,462],{"class":143},"env",[32,464,459],{"class":137},[32,466,467],{"class":38},"require",[32,469,470],{"class":137},"(",[32,472,473],{"class":48},"\"DISCORD_BOT_TOKEN\"",[32,475,476],{"class":137},")\n",[32,478,480,482,485,487,490,493,495,498],{"class":34,"line":479},29,[32,481,447],{"class":189},[32,483,484],{"class":450}," client",[32,486,454],{"class":137},[32,488,489],{"class":189},"new",[32,491,492],{"class":38}," Client",[32,494,430],{"class":137},[32,496,497],{"class":143},"intents:",[32,499,500],{"class":137}," [...] })\n",[32,502,504],{"class":34,"line":503},30,[32,505,166],{"emptyLinePlaceholder":165},[32,507,509,512,514,517,519,522,524,527,529,532,535,538,540,542],{"class":34,"line":508},31,[32,510,511],{"class":143},"        client",[32,513,459],{"class":137},[32,515,516],{"class":38},"on",[32,518,470],{"class":137},[32,520,521],{"class":143},"Events",[32,523,459],{"class":137},[32,525,526],{"class":143},"MessageCreate",[32,528,245],{"class":137},[32,530,531],{"class":189},"async",[32,533,534],{"class":137}," (",[32,536,537],{"class":143},"msg",[32,539,388],{"class":137},[32,541,391],{"class":189},[32,543,174],{"class":137},[32,545,547,550,552,554,556,559,562,564,566,569,571,574,576,579,581,584,587],{"class":34,"line":546},32,[32,548,549],{"class":127},"            if",[32,551,534],{"class":137},[32,553,438],{"class":143},[32,555,459],{"class":137},[32,557,558],{"class":143},"mentionOnly",[32,560,561],{"class":137}," && !",[32,563,537],{"class":143},[32,565,459],{"class":137},[32,567,568],{"class":143},"mentions",[32,570,459],{"class":137},[32,572,573],{"class":38},"has",[32,575,470],{"class":137},[32,577,578],{"class":143},"client",[32,580,459],{"class":137},[32,582,583],{"class":143},"user",[32,585,586],{"class":137},"!)) ",[32,588,589],{"class":127},"return\n",[32,591,593],{"class":34,"line":592},33,[32,594,166],{"emptyLinePlaceholder":165},[32,596,598,601,604,606,609,611,614],{"class":34,"line":597},34,[32,599,600],{"class":127},"            await",[32,602,603],{"class":143}," axon",[32,605,459],{"class":137},[32,607,608],{"class":38},"callHook",[32,610,470],{"class":137},[32,612,613],{"class":48},"\"discord:message.received\"",[32,615,616],{"class":137},", {\n",[32,618,620,623,626,628,631],{"class":34,"line":619},35,[32,621,622],{"class":143},"                content:",[32,624,625],{"class":143}," msg",[32,627,459],{"class":137},[32,629,630],{"class":143},"content",[32,632,150],{"class":137},[32,634,636,639,641,643,646,648,651],{"class":34,"line":635},36,[32,637,638],{"class":143},"                username:",[32,640,625],{"class":143},[32,642,459],{"class":137},[32,644,645],{"class":143},"author",[32,647,459],{"class":137},[32,649,650],{"class":143},"username",[32,652,150],{"class":137},[32,654,656,659,661,663,666],{"class":34,"line":655},37,[32,657,658],{"class":143},"                channelId:",[32,660,625],{"class":143},[32,662,459],{"class":137},[32,664,665],{"class":143},"channelId",[32,667,150],{"class":137},[32,669,671,674,676,678,680,682,684,686,688,691,693,695],{"class":34,"line":670},38,[32,672,673],{"class":38},"                reply",[32,675,327],{"class":143},[32,677,534],{"class":137},[32,679,380],{"class":143},[32,681,388],{"class":137},[32,683,391],{"class":189},[32,685,625],{"class":143},[32,687,459],{"class":137},[32,689,690],{"class":38},"reply",[32,692,470],{"class":137},[32,694,380],{"class":143},[32,696,697],{"class":137},"),\n",[32,699,701],{"class":34,"line":700},39,[32,702,703],{"class":137},"            })\n",[32,705,707],{"class":34,"line":706},40,[32,708,709],{"class":137},"        })\n",[32,711,713],{"class":34,"line":712},41,[32,714,166],{"emptyLinePlaceholder":165},[32,716,718,721,723,725,728,730,733],{"class":34,"line":717},42,[32,719,720],{"class":127},"        await",[32,722,484],{"class":143},[32,724,459],{"class":137},[32,726,727],{"class":38},"login",[32,729,470],{"class":137},[32,731,732],{"class":143},"token",[32,734,476],{"class":137},[32,736,738],{"class":34,"line":737},43,[32,739,166],{"emptyLinePlaceholder":165},[32,741,743,746,748,751,753,756,759,761,763,765,768],{"class":34,"line":742},44,[32,744,745],{"class":143},"        axon",[32,747,459],{"class":137},[32,749,750],{"class":38},"hook",[32,752,470],{"class":137},[32,754,755],{"class":48},"\"axon:agent:shutdown\"",[32,757,758],{"class":137},", () ",[32,760,391],{"class":189},[32,762,484],{"class":143},[32,764,459],{"class":137},[32,766,767],{"class":38},"destroy",[32,769,770],{"class":137},"())\n",[32,772,774],{"class":34,"line":773},45,[32,775,214],{"class":137},[32,777,779],{"class":34,"line":778},46,[32,780,781],{"class":137},"})\n",[109,783,438],{"id":438},[14,785,786,787,790,791,459],{},"Options make modules configurable at install time. The installing agent sets them in\n",[18,788,789],{},"axon.config.ts",". The module receives them in ",[18,792,793],{},"setup({ options })",[23,795,797],{"className":118,"code":796,"language":120,"meta":28,"style":28},"export default defineModule({\n    options: {\n        mentionOnly: {\n            type: \"boolean\",\n            default: false,\n            description: \"Only trigger when the bot is @mentioned.\",\n        },\n        channelIds: {\n            type: \"string\",\n            required: false,\n            description: \"Comma-separated channel IDs to listen on.\",\n        },\n    },\n})\n",[18,798,799,809,815,821,830,839,848,852,858,866,874,883,887,891],{"__ignoreMap":28},[32,800,801,803,805,807],{"class":34,"line":35},[32,802,128],{"class":127},[32,804,131],{"class":127},[32,806,134],{"class":38},[32,808,138],{"class":137},[32,810,811,813],{"class":34,"line":42},[32,812,225],{"class":143},[32,814,174],{"class":137},[32,816,817,819],{"class":34,"line":52},[32,818,233],{"class":143},[32,820,174],{"class":137},[32,822,823,826,828],{"class":34,"line":64},[32,824,825],{"class":143},"            type:",[32,827,242],{"class":48},[32,829,150],{"class":137},[32,831,832,835,837],{"class":34,"line":72},[32,833,834],{"class":143},"            default:",[32,836,251],{"class":189},[32,838,150],{"class":137},[32,840,841,843,846],{"class":34,"line":83},[32,842,197],{"class":143},[32,844,845],{"class":48}," \"Only trigger when the bot is @mentioned.\"",[32,847,150],{"class":137},[32,849,850],{"class":34,"line":93},[32,851,208],{"class":137},[32,853,854,856],{"class":34,"line":101},[32,855,260],{"class":143},[32,857,174],{"class":137},[32,859,860,862,864],{"class":34,"line":205},[32,861,825],{"class":143},[32,863,268],{"class":48},[32,865,150],{"class":137},[32,867,868,870,872],{"class":34,"line":211},[32,869,186],{"class":143},[32,871,251],{"class":189},[32,873,150],{"class":137},[32,875,876,878,881],{"class":34,"line":217},[32,877,197],{"class":143},[32,879,880],{"class":48}," \"Comma-separated channel IDs to listen on.\"",[32,882,150],{"class":137},[32,884,885],{"class":34,"line":222},[32,886,208],{"class":137},[32,888,889],{"class":34,"line":230},[32,890,214],{"class":137},[32,892,893],{"class":34,"line":257},[32,894,781],{"class":137},[14,896,897,898,901],{},"Options are typed and validated before ",[18,899,900],{},"setup"," runs. Use them to avoid hardcoding\nbehaviour that different agents will want to configure differently.",[109,903,905],{"id":904},"emits-and-the-hookprompt-pattern","emits and the hook\u002Fprompt pattern",[14,907,908,911,912,459],{},[18,909,910],{},"emits"," declares the hooks your module fires. Types flow through to the subscription\nsite — the agent author gets typed payloads when they call ",[18,913,914],{},"axon.hooks.hook(...)",[14,916,917],{},"The canonical pattern: the module emits the hook, the agent author loads a prompt\nwith the hook payload and calls the agent:",[23,919,921],{"className":118,"code":920,"language":120,"meta":28,"style":28},"\u002F\u002F agent's server\u002Fplugins\u002Fdiscord.ts\naxon.hooks.hook(\"discord:message.received\", async ({ content, username, reply }) => {\n    const prompt = await axon.prompt(\"discord\u002Fdiscord\", { content, username })\n    const result = await axon.request({ prompt })\n    await reply(result.text)\n})\n",[18,922,923,929,970,1007,1031,1050],{"__ignoreMap":28},[32,924,925],{"class":34,"line":35},[32,926,928],{"class":927},"sOLPB","\u002F\u002F agent's server\u002Fplugins\u002Fdiscord.ts\n",[32,930,931,933,935,938,940,942,944,946,948,950,953,955,957,959,961,963,966,968],{"class":34,"line":42},[32,932,433],{"class":143},[32,934,459],{"class":137},[32,936,937],{"class":143},"hooks",[32,939,459],{"class":137},[32,941,750],{"class":38},[32,943,470],{"class":137},[32,945,613],{"class":48},[32,947,245],{"class":137},[32,949,531],{"class":189},[32,951,952],{"class":137}," ({ ",[32,954,630],{"class":143},[32,956,245],{"class":137},[32,958,650],{"class":143},[32,960,245],{"class":137},[32,962,690],{"class":143},[32,964,965],{"class":137}," }) ",[32,967,391],{"class":189},[32,969,174],{"class":137},[32,971,972,975,978,980,983,985,987,990,992,995,998,1000,1002,1004],{"class":34,"line":52},[32,973,974],{"class":189},"    const",[32,976,977],{"class":450}," prompt",[32,979,454],{"class":137},[32,981,982],{"class":127},"await",[32,984,603],{"class":143},[32,986,459],{"class":137},[32,988,989],{"class":38},"prompt",[32,991,470],{"class":137},[32,993,994],{"class":48},"\"discord\u002Fdiscord\"",[32,996,997],{"class":137},", { ",[32,999,630],{"class":143},[32,1001,245],{"class":137},[32,1003,650],{"class":143},[32,1005,1006],{"class":137}," })\n",[32,1008,1009,1011,1014,1016,1018,1020,1022,1025,1027,1029],{"class":34,"line":64},[32,1010,974],{"class":189},[32,1012,1013],{"class":450}," result",[32,1015,454],{"class":137},[32,1017,982],{"class":127},[32,1019,603],{"class":143},[32,1021,459],{"class":137},[32,1023,1024],{"class":38},"request",[32,1026,430],{"class":137},[32,1028,989],{"class":143},[32,1030,1006],{"class":137},[32,1032,1033,1036,1039,1041,1044,1046,1048],{"class":34,"line":72},[32,1034,1035],{"class":127},"    await",[32,1037,1038],{"class":38}," reply",[32,1040,470],{"class":137},[32,1042,1043],{"class":143},"result",[32,1045,459],{"class":137},[32,1047,380],{"class":143},[32,1049,476],{"class":137},[32,1051,1052],{"class":34,"line":83},[32,1053,781],{"class":137},[14,1055,1056,1057,1060,1061,459],{},"Contribute the prompt alongside the hook so the agent author has something to start\nwith. Put it in ",[18,1058,1059],{},"src\u002Fprompts\u002F"," — it becomes available as ",[18,1062,1063],{},"axon.prompt(\"discord\u002Fdiscord\")",[109,1065,1067],{"id":1066},"setup-lifecycle","setup lifecycle",[14,1069,1070,1072],{},[18,1071,900],{}," runs once at agent boot. It is the right place to:",[1074,1075,1076,1083,1086,1092],"ul",{},[1077,1078,1079,1080],"li",{},"validate required env keys with ",[18,1081,1082],{},"axon.env.require()",[1077,1084,1085],{},"connect to external services (Discord client, IMAP connection, database)",[1077,1087,1088,1089],{},"register teardown on ",[18,1090,1091],{},"axon:agent:shutdown",[1077,1093,1094,1095,1098],{},"start polling loops, but only after ",[18,1096,1097],{},"server:ready"," fires",[23,1100,1102],{"className":118,"code":1101,"language":120,"meta":28,"style":28},"async setup({ axon, options }) {\n    \u002F\u002F validate first — fail fast if misconfigured\n    const token = axon.env.require(\"DISCORD_BOT_TOKEN\")\n\n    \u002F\u002F connect\n    const client = new Client({ ... })\n    await client.login(token)\n\n    \u002F\u002F always register teardown\n    axon.hook(\"axon:agent:shutdown\", () => client.destroy())\n\n    \u002F\u002F if you poll, wait for server:ready so all plugins are registered first\n    axon.hook(\"server:ready\", () => startPolling())\n}\n",[18,1103,1104,1120,1125,1149,1153,1158,1173,1189,1193,1198,1223,1227,1232,1254],{"__ignoreMap":28},[32,1105,1106,1108,1110,1112,1114,1116,1118],{"class":34,"line":35},[32,1107,531],{"class":143},[32,1109,427],{"class":38},[32,1111,430],{"class":137},[32,1113,433],{"class":143},[32,1115,245],{"class":137},[32,1117,438],{"class":143},[32,1119,441],{"class":137},[32,1121,1122],{"class":34,"line":42},[32,1123,1124],{"class":927},"    \u002F\u002F validate first — fail fast if misconfigured\n",[32,1126,1127,1129,1131,1133,1135,1137,1139,1141,1143,1145,1147],{"class":34,"line":52},[32,1128,974],{"class":189},[32,1130,451],{"class":450},[32,1132,454],{"class":137},[32,1134,433],{"class":143},[32,1136,459],{"class":137},[32,1138,462],{"class":143},[32,1140,459],{"class":137},[32,1142,467],{"class":38},[32,1144,470],{"class":137},[32,1146,473],{"class":48},[32,1148,476],{"class":137},[32,1150,1151],{"class":34,"line":64},[32,1152,166],{"emptyLinePlaceholder":165},[32,1154,1155],{"class":34,"line":72},[32,1156,1157],{"class":927},"    \u002F\u002F connect\n",[32,1159,1160,1162,1164,1166,1168,1170],{"class":34,"line":83},[32,1161,974],{"class":189},[32,1163,484],{"class":450},[32,1165,454],{"class":137},[32,1167,489],{"class":189},[32,1169,492],{"class":38},[32,1171,1172],{"class":137},"({ ... })\n",[32,1174,1175,1177,1179,1181,1183,1185,1187],{"class":34,"line":93},[32,1176,1035],{"class":127},[32,1178,484],{"class":143},[32,1180,459],{"class":137},[32,1182,727],{"class":38},[32,1184,470],{"class":137},[32,1186,732],{"class":143},[32,1188,476],{"class":137},[32,1190,1191],{"class":34,"line":101},[32,1192,166],{"emptyLinePlaceholder":165},[32,1194,1195],{"class":34,"line":205},[32,1196,1197],{"class":927},"    \u002F\u002F always register teardown\n",[32,1199,1200,1203,1205,1207,1209,1211,1213,1215,1217,1219,1221],{"class":34,"line":211},[32,1201,1202],{"class":143},"    axon",[32,1204,459],{"class":137},[32,1206,750],{"class":38},[32,1208,470],{"class":137},[32,1210,755],{"class":48},[32,1212,758],{"class":137},[32,1214,391],{"class":189},[32,1216,484],{"class":143},[32,1218,459],{"class":137},[32,1220,767],{"class":38},[32,1222,770],{"class":137},[32,1224,1225],{"class":34,"line":217},[32,1226,166],{"emptyLinePlaceholder":165},[32,1228,1229],{"class":34,"line":222},[32,1230,1231],{"class":927},"    \u002F\u002F if you poll, wait for server:ready so all plugins are registered first\n",[32,1233,1234,1236,1238,1240,1242,1245,1247,1249,1252],{"class":34,"line":230},[32,1235,1202],{"class":143},[32,1237,459],{"class":137},[32,1239,750],{"class":38},[32,1241,470],{"class":137},[32,1243,1244],{"class":48},"\"server:ready\"",[32,1246,758],{"class":137},[32,1248,391],{"class":189},[32,1250,1251],{"class":38}," startPolling",[32,1253,770],{"class":137},[32,1255,1256],{"class":34,"line":257},[32,1257,1258],{"class":137},"}\n",[14,1260,1261,1262,1264,1265,1267],{},"The ",[18,1263,1097],{}," hook matters for polling modules. Plugins that subscribe to your\nhooks need to be registered before you start emitting. ",[18,1266,1097],{}," fires after all\nplugins are loaded.",[109,1269,1271],{"id":1270},"adding-tools","Adding tools",[14,1273,1274,1275,1278,1279,1282,1283,1286,1287,327],{},"Export async functions from ",[18,1276,1277],{},"src\u002Ftools\u002F",". Each file becomes a namespace on\n",[18,1280,1281],{},"axon.tools",". A file named ",[18,1284,1285],{},"email.ts"," contributes ",[18,1288,1289],{},"axon.tools.email.*",[23,1291,1293],{"className":118,"code":1292,"language":120,"meta":28,"style":28},"\u002F\u002F src\u002Ftools\u002Femail.ts\nexport async function send(to: string, subject: string, body: string): Promise\u003Cvoid> {\n    const sgMail = (await import(\"@sendgrid\u002Fmail\")).default\n    sgMail.setApiKey(process.env.SENDGRID_API_KEY!)\n    await sgMail.send({ to, from: process.env.EMAIL_FROM!, subject, text: body })\n}\n",[18,1294,1295,1300,1353,1379,1406,1453],{"__ignoreMap":28},[32,1296,1297],{"class":34,"line":35},[32,1298,1299],{"class":927},"\u002F\u002F src\u002Ftools\u002Femail.ts\n",[32,1301,1302,1304,1307,1310,1313,1315,1318,1320,1322,1324,1327,1329,1331,1333,1336,1338,1340,1343,1346,1348,1350],{"class":34,"line":42},[32,1303,128],{"class":127},[32,1305,1306],{"class":189}," async",[32,1308,1309],{"class":189}," function",[32,1311,1312],{"class":38}," send",[32,1314,470],{"class":137},[32,1316,1317],{"class":143},"to",[32,1319,344],{"class":137},[32,1321,385],{"class":347},[32,1323,245],{"class":137},[32,1325,1326],{"class":143},"subject",[32,1328,344],{"class":137},[32,1330,385],{"class":347},[32,1332,245],{"class":137},[32,1334,1335],{"class":143},"body",[32,1337,344],{"class":137},[32,1339,385],{"class":347},[32,1341,1342],{"class":137},"): ",[32,1344,1345],{"class":347},"Promise",[32,1347,397],{"class":137},[32,1349,400],{"class":347},[32,1351,1352],{"class":137},"> {\n",[32,1354,1355,1357,1360,1363,1365,1368,1370,1373,1376],{"class":34,"line":52},[32,1356,974],{"class":189},[32,1358,1359],{"class":450}," sgMail",[32,1361,1362],{"class":137}," = (",[32,1364,982],{"class":127},[32,1366,1367],{"class":189}," import",[32,1369,470],{"class":137},[32,1371,1372],{"class":48},"\"@sendgrid\u002Fmail\"",[32,1374,1375],{"class":137},")).",[32,1377,1378],{"class":143},"default\n",[32,1380,1381,1384,1386,1389,1391,1394,1396,1398,1400,1403],{"class":34,"line":64},[32,1382,1383],{"class":143},"    sgMail",[32,1385,459],{"class":137},[32,1387,1388],{"class":38},"setApiKey",[32,1390,470],{"class":137},[32,1392,1393],{"class":143},"process",[32,1395,459],{"class":137},[32,1397,462],{"class":143},[32,1399,459],{"class":137},[32,1401,1402],{"class":450},"SENDGRID_API_KEY",[32,1404,1405],{"class":137},"!)\n",[32,1407,1408,1410,1412,1414,1417,1419,1421,1423,1426,1429,1431,1433,1435,1438,1441,1443,1445,1448,1451],{"class":34,"line":72},[32,1409,1035],{"class":127},[32,1411,1359],{"class":143},[32,1413,459],{"class":137},[32,1415,1416],{"class":38},"send",[32,1418,430],{"class":137},[32,1420,1317],{"class":143},[32,1422,245],{"class":137},[32,1424,1425],{"class":143},"from:",[32,1427,1428],{"class":143}," process",[32,1430,459],{"class":137},[32,1432,462],{"class":143},[32,1434,459],{"class":137},[32,1436,1437],{"class":450},"EMAIL_FROM",[32,1439,1440],{"class":137},"!, ",[32,1442,1326],{"class":143},[32,1444,245],{"class":137},[32,1446,1447],{"class":143},"text:",[32,1449,1450],{"class":143}," body",[32,1452,1006],{"class":137},[32,1454,1455],{"class":34,"line":83},[32,1456,1258],{"class":137},[14,1458,1459,1460,1463,1464,1467],{},"Run ",[18,1461,1462],{},"axon module prepare"," to generate type declarations. The installing agent gets\n",[18,1465,1466],{},"axon.tools.email.send(...)"," fully typed.",[109,1469,1471],{"id":1470},"adding-routes","Adding routes",[14,1473,1474,1475,1478],{},"Routes in ",[18,1476,1477],{},"server\u002Fapi\u002F"," are mounted on the parent agent's HTTP server. The canonical\nuse is webhook receivers — verify the signature, normalize the payload, emit the hook:",[23,1480,1482],{"className":118,"code":1481,"language":120,"meta":28,"style":28},"\u002F\u002F server\u002Fapi\u002Fgithub.post.ts\nexport default defineEventHandler(async (event) => {\n    const rawBody = (await readRawBody(event)) ?? \"\"\n    const sig = getRequestHeader(event, \"x-hub-signature-256\") ?? \"\"\n\n    if (!verifySignature(rawBody, sig, process.env.GITHUB_WEBHOOK_SECRET!)) {\n        throw createError({ statusCode: 403, message: \"invalid signature\" })\n    }\n\n    const payload = JSON.parse(rawBody)\n\n    await axon.callHook(\"github:issue.opened\", {\n        number: payload.issue.number,\n        title: payload.issue.title,\n    })\n\n    return { ok: true }\n})\n",[18,1483,1484,1489,1513,1537,1563,1567,1604,1631,1636,1640,1663,1667,1684,1703,1721,1726,1730,1745],{"__ignoreMap":28},[32,1485,1486],{"class":34,"line":35},[32,1487,1488],{"class":927},"\u002F\u002F server\u002Fapi\u002Fgithub.post.ts\n",[32,1490,1491,1493,1495,1498,1500,1502,1504,1507,1509,1511],{"class":34,"line":42},[32,1492,128],{"class":127},[32,1494,131],{"class":127},[32,1496,1497],{"class":38}," defineEventHandler",[32,1499,470],{"class":137},[32,1501,531],{"class":189},[32,1503,534],{"class":137},[32,1505,1506],{"class":143},"event",[32,1508,388],{"class":137},[32,1510,391],{"class":189},[32,1512,174],{"class":137},[32,1514,1515,1517,1520,1522,1524,1527,1529,1531,1534],{"class":34,"line":52},[32,1516,974],{"class":189},[32,1518,1519],{"class":450}," rawBody",[32,1521,1362],{"class":137},[32,1523,982],{"class":127},[32,1525,1526],{"class":38}," readRawBody",[32,1528,470],{"class":137},[32,1530,1506],{"class":143},[32,1532,1533],{"class":137},")) ?? ",[32,1535,1536],{"class":48},"\"\"\n",[32,1538,1539,1541,1544,1546,1549,1551,1553,1555,1558,1561],{"class":34,"line":64},[32,1540,974],{"class":189},[32,1542,1543],{"class":450}," sig",[32,1545,454],{"class":137},[32,1547,1548],{"class":38},"getRequestHeader",[32,1550,470],{"class":137},[32,1552,1506],{"class":143},[32,1554,245],{"class":137},[32,1556,1557],{"class":48},"\"x-hub-signature-256\"",[32,1559,1560],{"class":137},") ?? ",[32,1562,1536],{"class":48},[32,1564,1565],{"class":34,"line":72},[32,1566,166],{"emptyLinePlaceholder":165},[32,1568,1569,1572,1575,1578,1580,1583,1585,1588,1590,1592,1594,1596,1598,1601],{"class":34,"line":83},[32,1570,1571],{"class":127},"    if",[32,1573,1574],{"class":137}," (!",[32,1576,1577],{"class":38},"verifySignature",[32,1579,470],{"class":137},[32,1581,1582],{"class":143},"rawBody",[32,1584,245],{"class":137},[32,1586,1587],{"class":143},"sig",[32,1589,245],{"class":137},[32,1591,1393],{"class":143},[32,1593,459],{"class":137},[32,1595,462],{"class":143},[32,1597,459],{"class":137},[32,1599,1600],{"class":450},"GITHUB_WEBHOOK_SECRET",[32,1602,1603],{"class":137},"!)) {\n",[32,1605,1606,1609,1612,1614,1617,1621,1623,1626,1629],{"class":34,"line":93},[32,1607,1608],{"class":127},"        throw",[32,1610,1611],{"class":38}," createError",[32,1613,430],{"class":137},[32,1615,1616],{"class":143},"statusCode:",[32,1618,1620],{"class":1619},"sDGX8"," 403",[32,1622,245],{"class":137},[32,1624,1625],{"class":143},"message:",[32,1627,1628],{"class":48}," \"invalid signature\"",[32,1630,1006],{"class":137},[32,1632,1633],{"class":34,"line":101},[32,1634,1635],{"class":137},"    }\n",[32,1637,1638],{"class":34,"line":205},[32,1639,166],{"emptyLinePlaceholder":165},[32,1641,1642,1644,1647,1649,1652,1654,1657,1659,1661],{"class":34,"line":211},[32,1643,974],{"class":189},[32,1645,1646],{"class":450}," payload",[32,1648,454],{"class":137},[32,1650,1651],{"class":450},"JSON",[32,1653,459],{"class":137},[32,1655,1656],{"class":38},"parse",[32,1658,470],{"class":137},[32,1660,1582],{"class":143},[32,1662,476],{"class":137},[32,1664,1665],{"class":34,"line":217},[32,1666,166],{"emptyLinePlaceholder":165},[32,1668,1669,1671,1673,1675,1677,1679,1682],{"class":34,"line":222},[32,1670,1035],{"class":127},[32,1672,603],{"class":143},[32,1674,459],{"class":137},[32,1676,608],{"class":38},[32,1678,470],{"class":137},[32,1680,1681],{"class":48},"\"github:issue.opened\"",[32,1683,616],{"class":137},[32,1685,1686,1689,1691,1693,1696,1698,1701],{"class":34,"line":230},[32,1687,1688],{"class":143},"        number:",[32,1690,1646],{"class":143},[32,1692,459],{"class":137},[32,1694,1695],{"class":143},"issue",[32,1697,459],{"class":137},[32,1699,1700],{"class":143},"number",[32,1702,150],{"class":137},[32,1704,1705,1708,1710,1712,1714,1716,1719],{"class":34,"line":257},[32,1706,1707],{"class":143},"        title:",[32,1709,1646],{"class":143},[32,1711,459],{"class":137},[32,1713,1695],{"class":143},[32,1715,459],{"class":137},[32,1717,1718],{"class":143},"title",[32,1720,150],{"class":137},[32,1722,1723],{"class":34,"line":281},[32,1724,1725],{"class":137},"    })\n",[32,1727,1728],{"class":34,"line":303},[32,1729,166],{"emptyLinePlaceholder":165},[32,1731,1732,1735,1737,1740,1742],{"class":34,"line":308},[32,1733,1734],{"class":127},"    return",[32,1736,236],{"class":137},[32,1738,1739],{"class":143},"ok:",[32,1741,190],{"class":189},[32,1743,1744],{"class":137}," }\n",[32,1746,1747],{"class":34,"line":313},[32,1748,781],{"class":137},[14,1750,1751],{},"The module ships the route. The agent author never writes webhook verification code.",[109,1753,1755],{"id":1754},"npm-dependencies","npm dependencies",[14,1757,1758,1759,1762],{},"Declare external packages in ",[18,1760,1761],{},"package.json",". They are incorporated into the parent\nagent's environment on install:",[23,1764,1768],{"className":1765,"code":1766,"language":1767,"meta":28,"style":28},"language-json shiki shiki-themes dark-plus","{\n    \"name\": \"my-module\",\n    \"version\": \"0.1.0\",\n    \"dependencies\": {\n        \"discord.js\": \"^14.16.3\"\n    }\n}\n","json",[18,1769,1770,1775,1787,1799,1807,1817,1821],{"__ignoreMap":28},[32,1771,1772],{"class":34,"line":35},[32,1773,1774],{"class":137},"{\n",[32,1776,1777,1780,1782,1785],{"class":34,"line":42},[32,1778,1779],{"class":143},"    \"name\"",[32,1781,344],{"class":137},[32,1783,1784],{"class":48},"\"my-module\"",[32,1786,150],{"class":137},[32,1788,1789,1792,1794,1797],{"class":34,"line":52},[32,1790,1791],{"class":143},"    \"version\"",[32,1793,344],{"class":137},[32,1795,1796],{"class":48},"\"0.1.0\"",[32,1798,150],{"class":137},[32,1800,1801,1804],{"class":34,"line":64},[32,1802,1803],{"class":143},"    \"dependencies\"",[32,1805,1806],{"class":137},": {\n",[32,1808,1809,1812,1814],{"class":34,"line":72},[32,1810,1811],{"class":143},"        \"discord.js\"",[32,1813,344],{"class":137},[32,1815,1816],{"class":48},"\"^14.16.3\"\n",[32,1818,1819],{"class":34,"line":83},[32,1820,1635],{"class":137},[32,1822,1823],{"class":34,"line":93},[32,1824,1258],{"class":137},[14,1826,1827],{},"Keep dependencies minimal. Every package you add is installed into every agent that\nuses the module.",[109,1829,1831],{"id":1830},"testing","Testing",[14,1833,1834,1835,1840],{},"Module tests boot a parent agent with the module installed and assert on the contribution\nsurface — tools registered, prompts renderable, hooks firing correctly. See\n",[1836,1837,1839],"a",{"href":1838},"\u002Fdocs\u002Fv2\u002Fmodules\u002Ftests","Module tests"," for the full reference.",[109,1842,1844],{"id":1843},"publishing","Publishing",[14,1846,1847],{},"When the module is ready:",[23,1849,1851],{"className":25,"code":1850,"language":27,"meta":28,"style":28},"axon module prepare   # generate type declarations\naxon module deploy    # publish to registry\n",[18,1852,1853,1866],{"__ignoreMap":28},[32,1854,1855,1857,1860,1863],{"class":34,"line":35},[32,1856,433],{"class":38},[32,1858,1859],{"class":48}," module",[32,1861,1862],{"class":48}," prepare",[32,1864,1865],{"class":927},"   # generate type declarations\n",[32,1867,1868,1870,1872,1875],{"class":34,"line":42},[32,1869,433],{"class":38},[32,1871,1859],{"class":48},[32,1873,1874],{"class":48}," deploy",[32,1876,1877],{"class":927},"    # publish to registry\n",[14,1879,1880,1881,1884],{},"See ",[1836,1882,1844],{"href":1883},"\u002Fdocs\u002Fv2\u002Fmodules\u002Fpublishing"," for versioning and registry identity.",[1886,1887,1888],"style",{},"html pre.shiki code .sCudf, html code.shiki .sCudf{--shiki-default:#DCDCAA}html pre.shiki code .sKc5r, html code.shiki .sKc5r{--shiki-default:#CE9178}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sYbnZ, html code.shiki .sYbnZ{--shiki-default:#C586C0}html pre.shiki code .sTNBD, html code.shiki .sTNBD{--shiki-default:#D4D4D4}html pre.shiki code .s3F5K, html code.shiki .s3F5K{--shiki-default:#9CDCFE}html pre.shiki code .scz_3, html code.shiki .scz_3{--shiki-default:#569CD6}html pre.shiki code .sNl3T, html code.shiki .sNl3T{--shiki-default:#4EC9B0}html pre.shiki code .s9McN, html code.shiki .s9McN{--shiki-default:#4FC1FF}html pre.shiki code .sOLPB, html code.shiki .sOLPB{--shiki-default:#6A9955}html pre.shiki code .sDGX8, html code.shiki .sDGX8{--shiki-default:#B5CEA8}",{"title":28,"searchDepth":42,"depth":42,"links":1890},[1891,1892,1893,1894,1895,1896,1897,1898,1899],{"id":111,"depth":42,"text":112},{"id":438,"depth":42,"text":438},{"id":904,"depth":42,"text":905},{"id":1066,"depth":42,"text":1067},{"id":1270,"depth":42,"text":1271},{"id":1470,"depth":42,"text":1471},{"id":1754,"depth":42,"text":1755},{"id":1830,"depth":42,"text":1831},{"id":1843,"depth":42,"text":1844},"A module starts as a folder with a module.config.ts. Everything else is optional\ndepending on what the module contributes.","md",{},"\u002Fdocs\u002Fv2\u002Fmodules\u002Fbuilding",{"title":5,"description":1900},"docs\u002Fv2\u002Fmodules\u002Fbuilding","nh2ht4MM6vDgQyNxkLZgu4NfS4OS0W03HDEaHnImpb0",1785671806660]