@xano/sdk 1.0.0

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 (336) hide show
  1. package/CHANGELOG.md +788 -0
  2. package/LICENSE +21 -0
  3. package/README.md +541 -0
  4. package/dist/.build-fingerprint +1 -0
  5. package/dist/agent-file-refresh-ZEMK33AX.js +38 -0
  6. package/dist/answer-shape-K67TAAPY.js +33 -0
  7. package/dist/backend-dir-2U53CIAL.js +39 -0
  8. package/dist/backend-slot-7QXW2QYC.js +28 -0
  9. package/dist/bin.d.ts +1 -0
  10. package/dist/bin.js +255 -0
  11. package/dist/branch-NCRNWQBQ.js +61 -0
  12. package/dist/branch-commands-ACIRCEQJ.js +79 -0
  13. package/dist/bundle.d.ts +223 -0
  14. package/dist/bundle.js +153 -0
  15. package/dist/capture-WAXPD7DR.js +41 -0
  16. package/dist/chunk-27GWNP2I.js +23 -0
  17. package/dist/chunk-2CMJZDCL.js +177 -0
  18. package/dist/chunk-2EZ3ZJZZ.js +302 -0
  19. package/dist/chunk-2GNFDZA2.js +390 -0
  20. package/dist/chunk-2MNLRIOX.js +11 -0
  21. package/dist/chunk-2TFRR6JV.js +104 -0
  22. package/dist/chunk-2ZUEE4EK.js +408 -0
  23. package/dist/chunk-36JUQZVQ.js +22 -0
  24. package/dist/chunk-3DRXWLJ5.js +83 -0
  25. package/dist/chunk-3JDU4VRL.js +137 -0
  26. package/dist/chunk-3KZCLN4Z.js +241 -0
  27. package/dist/chunk-3LDULPRE.js +168 -0
  28. package/dist/chunk-3M4LFYNQ.js +111 -0
  29. package/dist/chunk-3XI5CTUE.js +22 -0
  30. package/dist/chunk-3YW23UFH.js +50 -0
  31. package/dist/chunk-45UIT5T2.js +862 -0
  32. package/dist/chunk-4BW2SG2N.js +46 -0
  33. package/dist/chunk-4KHEMC32.js +79 -0
  34. package/dist/chunk-4POWHT2K.js +321 -0
  35. package/dist/chunk-4Q2GN7NW.js +192 -0
  36. package/dist/chunk-5A2PLKZB.js +70 -0
  37. package/dist/chunk-5FBYXK5P.js +159 -0
  38. package/dist/chunk-5HVXPTU4.js +323 -0
  39. package/dist/chunk-5QU2P5CA.js +304 -0
  40. package/dist/chunk-5V5XU32D.js +191 -0
  41. package/dist/chunk-5ZSO4T2G.js +10 -0
  42. package/dist/chunk-622HUCZT.js +10 -0
  43. package/dist/chunk-65HWOMGV.js +177 -0
  44. package/dist/chunk-6ELFRN3P.js +365 -0
  45. package/dist/chunk-6HO3NCSJ.js +1055 -0
  46. package/dist/chunk-6IUMKYWI.js +457 -0
  47. package/dist/chunk-6KEB3C3U.js +65 -0
  48. package/dist/chunk-6MR5TI5V.js +987 -0
  49. package/dist/chunk-6NNTNXN7.js +157 -0
  50. package/dist/chunk-6PBR37LN.js +97 -0
  51. package/dist/chunk-6QXLMMT3.js +155 -0
  52. package/dist/chunk-6ZGJCBZS.js +224 -0
  53. package/dist/chunk-73IVBKMA.js +250 -0
  54. package/dist/chunk-7G3CTU7I.js +286 -0
  55. package/dist/chunk-A3UDOZB6.js +222 -0
  56. package/dist/chunk-A6E77GDW.js +85 -0
  57. package/dist/chunk-AHJWCYQO.js +660 -0
  58. package/dist/chunk-AKNCFVKZ.js +36 -0
  59. package/dist/chunk-AM4FKL4J.js +237 -0
  60. package/dist/chunk-AN6BL47T.js +26 -0
  61. package/dist/chunk-AOSYVMUS.js +147 -0
  62. package/dist/chunk-APWXY5SL.js +182 -0
  63. package/dist/chunk-AWEIIR2U.js +218 -0
  64. package/dist/chunk-AWQ7BQTQ.js +73 -0
  65. package/dist/chunk-BAUZOWGX.js +393 -0
  66. package/dist/chunk-BHACRS6R.js +940 -0
  67. package/dist/chunk-BWPNT55T.js +680 -0
  68. package/dist/chunk-C6RIMRU4.js +187 -0
  69. package/dist/chunk-CGVQZXYG.js +263 -0
  70. package/dist/chunk-CIMQZRRE.js +162 -0
  71. package/dist/chunk-CJHQKJU4.js +113 -0
  72. package/dist/chunk-CTMO46PK.js +1897 -0
  73. package/dist/chunk-D53J55C5.js +106 -0
  74. package/dist/chunk-D7VGG3WS.js +90 -0
  75. package/dist/chunk-DAD3ONIS.js +245 -0
  76. package/dist/chunk-DGWHAVRG.js +162 -0
  77. package/dist/chunk-DX4QR4B2.js +483 -0
  78. package/dist/chunk-EHP3WPEG.js +21 -0
  79. package/dist/chunk-ELP2HMXC.js +432 -0
  80. package/dist/chunk-ERVIASGL.js +21 -0
  81. package/dist/chunk-ET4Y4XCB.js +865 -0
  82. package/dist/chunk-ETO6LGLD.js +4750 -0
  83. package/dist/chunk-EUUUMY7J.js +24 -0
  84. package/dist/chunk-FQJ2TTHP.js +218 -0
  85. package/dist/chunk-FQOF7E2E.js +133 -0
  86. package/dist/chunk-FWD7AL6N.js +21 -0
  87. package/dist/chunk-FXFV3KU6.js +138 -0
  88. package/dist/chunk-FXRQOWTE.js +171 -0
  89. package/dist/chunk-G554NZNI.js +210 -0
  90. package/dist/chunk-GSGDWJB5.js +13 -0
  91. package/dist/chunk-H2V3N23D.js +66 -0
  92. package/dist/chunk-H5C7MBCR.js +159 -0
  93. package/dist/chunk-HHN734BU.js +371 -0
  94. package/dist/chunk-HRVBLCVJ.js +243 -0
  95. package/dist/chunk-HVRUQXGD.js +1264 -0
  96. package/dist/chunk-I4F5TCJH.js +130 -0
  97. package/dist/chunk-IDOTCMDV.js +57 -0
  98. package/dist/chunk-IGOLHKWF.js +427 -0
  99. package/dist/chunk-JGE7CAWB.js +148 -0
  100. package/dist/chunk-JTUKLCXS.js +92 -0
  101. package/dist/chunk-JZGVVJSI.js +66 -0
  102. package/dist/chunk-K4WX6UUU.js +923 -0
  103. package/dist/chunk-K5BBGTYZ.js +217 -0
  104. package/dist/chunk-K6RXBGUB.js +427 -0
  105. package/dist/chunk-KTSDSD36.js +165 -0
  106. package/dist/chunk-L5JYN3NM.js +1038 -0
  107. package/dist/chunk-LIEOKXKD.js +354 -0
  108. package/dist/chunk-LLNZGEFK.js +51 -0
  109. package/dist/chunk-LPECSJ2J.js +55 -0
  110. package/dist/chunk-LQCMKAJ7.js +413 -0
  111. package/dist/chunk-LRN4ZJCQ.js +1145 -0
  112. package/dist/chunk-MJ4ZVHKN.js +17 -0
  113. package/dist/chunk-MXHSQOC4.js +3061 -0
  114. package/dist/chunk-N5GKCWE5.js +367 -0
  115. package/dist/chunk-NA3PXMEF.js +10 -0
  116. package/dist/chunk-NAQDMEHV.js +76 -0
  117. package/dist/chunk-NWKXM5RD.js +109 -0
  118. package/dist/chunk-NZOWWHU5.js +196 -0
  119. package/dist/chunk-OHX6MIUZ.js +184 -0
  120. package/dist/chunk-OOXWYAA4.js +15 -0
  121. package/dist/chunk-OPHB5ORL.js +1990 -0
  122. package/dist/chunk-OXA6PSZT.js +1725 -0
  123. package/dist/chunk-P2HAGF6H.js +73 -0
  124. package/dist/chunk-PTHVMXZI.js +3036 -0
  125. package/dist/chunk-PZ5AY32C.js +10 -0
  126. package/dist/chunk-Q325JKYB.js +189 -0
  127. package/dist/chunk-Q7DGTQ5G.js +255 -0
  128. package/dist/chunk-QCNYHBL7.js +601 -0
  129. package/dist/chunk-QO6BVGS5.js +145 -0
  130. package/dist/chunk-QXTKSOFI.js +76 -0
  131. package/dist/chunk-ROY4YAEQ.js +66 -0
  132. package/dist/chunk-RTJRZFFY.js +447 -0
  133. package/dist/chunk-SAVJUS2X.js +128 -0
  134. package/dist/chunk-SFLHVCKX.js +173 -0
  135. package/dist/chunk-STSFNCY4.js +127 -0
  136. package/dist/chunk-SWBF2LTK.js +170 -0
  137. package/dist/chunk-T5KELPAR.js +109 -0
  138. package/dist/chunk-TFFGOFVZ.js +1481 -0
  139. package/dist/chunk-TLHIFK4M.js +256 -0
  140. package/dist/chunk-TLQE7ZN3.js +156 -0
  141. package/dist/chunk-TPHWJ7UG.js +1981 -0
  142. package/dist/chunk-TRJCALW3.js +291 -0
  143. package/dist/chunk-TTEIYD5P.js +133 -0
  144. package/dist/chunk-U64O2MXY.js +407 -0
  145. package/dist/chunk-UCQ2VUTG.js +630 -0
  146. package/dist/chunk-UTI5G6OJ.js +44 -0
  147. package/dist/chunk-UVHDMMIP.js +188 -0
  148. package/dist/chunk-UZ6QQWJT.js +120 -0
  149. package/dist/chunk-VAX3UZOY.js +686 -0
  150. package/dist/chunk-W3HBUKFO.js +57 -0
  151. package/dist/chunk-WKQLGKCL.js +2823 -0
  152. package/dist/chunk-WQFNASK4.js +560 -0
  153. package/dist/chunk-WRBKLYSJ.js +224 -0
  154. package/dist/chunk-WTPZRR4I.js +17 -0
  155. package/dist/chunk-WZDJ2LLX.js +2385 -0
  156. package/dist/chunk-X2NL4KUO.js +14 -0
  157. package/dist/chunk-X4DVXBFY.js +38 -0
  158. package/dist/chunk-X677DMZM.js +7050 -0
  159. package/dist/chunk-XBKMWY7F.js +612 -0
  160. package/dist/chunk-XNCP4KYO.js +78 -0
  161. package/dist/chunk-XVBGLISP.js +35 -0
  162. package/dist/chunk-XW6KISBB.js +1192 -0
  163. package/dist/chunk-YGZHHAAB.js +8156 -0
  164. package/dist/chunk-YHEFQUQB.js +186 -0
  165. package/dist/chunk-YLDALTWZ.js +653 -0
  166. package/dist/chunk-YU4HSQW7.js +229 -0
  167. package/dist/chunk-ZDPP6RKB.js +27 -0
  168. package/dist/chunk-ZFDZKNGG.js +194 -0
  169. package/dist/chunk-ZIY7RNUT.js +49 -0
  170. package/dist/chunk-ZYJP6BOO.js +30 -0
  171. package/dist/cli.d.ts +1111 -0
  172. package/dist/cli.js +165 -0
  173. package/dist/codegen-command-VR2GWR6C.js +173 -0
  174. package/dist/codegen.d.ts +115 -0
  175. package/dist/codegen.js +19 -0
  176. package/dist/commands-GJMJ4XYZ.js +76 -0
  177. package/dist/completion-ZQLO6PB6.js +738 -0
  178. package/dist/config-MBYGCAFK.js +136 -0
  179. package/dist/deploy-command-JVZPWHMK.js +180 -0
  180. package/dist/deploy-source-3W45CXMP.js +81 -0
  181. package/dist/dev-env-sync-HN44K4D5.js +188 -0
  182. package/dist/diagnostics-UEAAHWVF.js +30 -0
  183. package/dist/display-name-hint-ABBPVQIR.js +46 -0
  184. package/dist/emit-CyhBDxli.d.ts +7007 -0
  185. package/dist/env-example-refresh-ET5HMMDW.js +126 -0
  186. package/dist/env-pull-command-TNMNB3CV.js +323 -0
  187. package/dist/env-target-O4HRKTGZ.js +40 -0
  188. package/dist/env-var-command-ZIRDCAZA.js +427 -0
  189. package/dist/ephemeral-P7F3PMAD.js +54 -0
  190. package/dist/ephemeral-command-CLJJKOFT.js +592 -0
  191. package/dist/ephemeral-state-PYCBSOGX.js +61 -0
  192. package/dist/file-lock-PT74GWYW.js +10 -0
  193. package/dist/generate-command-AYEOLA26.js +510 -0
  194. package/dist/gitignore-3SJ3WNRG.js +51 -0
  195. package/dist/guards-4EBNCZDC.js +162 -0
  196. package/dist/hosted-file-PSHJB3HX.js +28 -0
  197. package/dist/impersonate-command-5JAS3WSV.js +152 -0
  198. package/dist/import-EQSFU6JC.js +19 -0
  199. package/dist/index.d.ts +5470 -0
  200. package/dist/index.js +371 -0
  201. package/dist/init-command-X2FR4UAK.js +125 -0
  202. package/dist/init-modules-RY7UHAOL.js +50 -0
  203. package/dist/init-web-M4YZSZK6.js +42 -0
  204. package/dist/internal.d.ts +179 -0
  205. package/dist/internal.js +2634 -0
  206. package/dist/io-RC56QSN5.js +26 -0
  207. package/dist/keep-data-merge-7MPLVAVI.js +1119 -0
  208. package/dist/lambda-config-reconcile-OUZ4OYAB.js +116 -0
  209. package/dist/landed-753YLJ5H.js +40 -0
  210. package/dist/landing-record-SU52SKXU.js +61 -0
  211. package/dist/live-diff-YONW2VI3.js +65 -0
  212. package/dist/local-engine-choice-CH5X3W7E.js +79 -0
  213. package/dist/local-engine-command-Z3OL7VOD.js +766 -0
  214. package/dist/local-engine-config-5U3SC434.js +39 -0
  215. package/dist/local-engine-deploy-PCQ4SNPB.js +39 -0
  216. package/dist/local-engine-process-EHTE5LLU.js +63 -0
  217. package/dist/local-engine-state-A4FTRFAF.js +32 -0
  218. package/dist/local-engine-update-check-6L645VBL.js +162 -0
  219. package/dist/lock-MRRHW5GS.js +87 -0
  220. package/dist/lock-commands-2QG25HL7.js +963 -0
  221. package/dist/login-command-QPYRWOXZ.js +538 -0
  222. package/dist/logout-command-WOIQLDP6.js +56 -0
  223. package/dist/loop-ALIZLOSX.js +191 -0
  224. package/dist/marketplace-command-K5YL42NO.js +977 -0
  225. package/dist/marketplace-resolve-LY32JES5.js +40 -0
  226. package/dist/meta-client-YXGPW53C.js +244 -0
  227. package/dist/module-manifest-JFGHU2TC.js +38 -0
  228. package/dist/near-key.d.ts +13 -0
  229. package/dist/near-key.js +8 -0
  230. package/dist/node.d.ts +95 -0
  231. package/dist/node.js +969 -0
  232. package/dist/operation-outcome-TZSESNC7.js +51 -0
  233. package/dist/output-target-AL3I44GG.js +47 -0
  234. package/dist/package-manager-PRAHUSCN.js +34 -0
  235. package/dist/plan-disclosure-6G4MLZKD.js +114 -0
  236. package/dist/plugin-contributions-WRNY5ABG.js +28 -0
  237. package/dist/plugin-prompt-4VA3B4FF.js +20 -0
  238. package/dist/plugin-questions-JTSYA74Q.js +24 -0
  239. package/dist/plugin.d.ts +396 -0
  240. package/dist/plugin.js +1 -0
  241. package/dist/preflight-command-2T5Z36MI.js +321 -0
  242. package/dist/profile-command-AH3PIM4X.js +800 -0
  243. package/dist/profile-pointer-GRWJU3YD.js +31 -0
  244. package/dist/profile-select-74TRDEPD.js +23 -0
  245. package/dist/project-dependency-KSH63ODT.js +35 -0
  246. package/dist/project-sdk-REPPOIBD.js +24 -0
  247. package/dist/promote-command-PCWT5N6S.js +140 -0
  248. package/dist/prompt-II3F3LND.js +27 -0
  249. package/dist/publish-command-TTDUUJI5.js +414 -0
  250. package/dist/pull-command-VBO7C4S3.js +161 -0
  251. package/dist/pull-superseded-GJWLG7XO.js +20 -0
  252. package/dist/release-command-N4ZPKMHR.js +146 -0
  253. package/dist/release-ns-command-3KFDMZ6G.js +1745 -0
  254. package/dist/response-HlXahv4K.d.ts +925 -0
  255. package/dist/retry-command-AS5EVREX.js +34 -0
  256. package/dist/routes-manifest-SNWUVBT6.js +43 -0
  257. package/dist/runtime-XETHVF33.js +14 -0
  258. package/dist/scaffold.d.ts +1911 -0
  259. package/dist/scaffold.js +107 -0
  260. package/dist/secrets-file-LVZD6IIM.js +56 -0
  261. package/dist/secrets-fill-command-TTCPSVXN.js +218 -0
  262. package/dist/seed-VU7C3QYS.js +46 -0
  263. package/dist/sent-writes-N7X4OVXT.js +22 -0
  264. package/dist/source-resolve-S2XA3XZF.js +83 -0
  265. package/dist/static-host-B2Y6AG3W.js +352 -0
  266. package/dist/status-command-RUDXTF7I.js +554 -0
  267. package/dist/store-6VNCD4D7.js +20 -0
  268. package/dist/store-ARM6VMO5.js +98 -0
  269. package/dist/store-DuYfzKlW.d.ts +3144 -0
  270. package/dist/suggest-6FOOYKHG.js +12 -0
  271. package/dist/sveltekit.d.ts +154 -0
  272. package/dist/sveltekit.js +109 -0
  273. package/dist/sync-contributions-UCEROO26.js +415 -0
  274. package/dist/table-ENAVMITR.js +42 -0
  275. package/dist/tables-command-GUPXXSB5.js +156 -0
  276. package/dist/tenant-IIHPOHGP.js +42 -0
  277. package/dist/tenant-command-D5HGI2X4.js +965 -0
  278. package/dist/test-command-TIKZMDPZ.js +476 -0
  279. package/dist/tests-BGGIOK3L.js +26 -0
  280. package/dist/token-NICJUTMT.js +72 -0
  281. package/dist/toolchain-hooks-GHKLUJOH.js +96 -0
  282. package/dist/toolchain-modules-5EAZUONB.js +24 -0
  283. package/dist/tracked-backend-EIBUMDZA.js +80 -0
  284. package/dist/update-check-2W2NLDTC.js +136 -0
  285. package/dist/upgrade-command-QHNFWM7O.js +531 -0
  286. package/dist/verify-rollout-DRCK5TDD.js +61 -0
  287. package/dist/whoami-command-STGINTXH.js +51 -0
  288. package/dist/workspace-2CLHU2LK.js +93 -0
  289. package/dist/workspace-binding-56PIIPCP.js +54 -0
  290. package/dist/workspace-command-JEXQETJD.js +535 -0
  291. package/dist/workspace-env-F2RFMSC5.js +70 -0
  292. package/dist/workspace-export-OJSH2HIW.js +21 -0
  293. package/dist/writable-VA7OISSK.js +25 -0
  294. package/dist/xano-ICFUSPFR.js +42 -0
  295. package/dist/xanosdk-import-GN42TO2B.js +56 -0
  296. package/dist/xanosdk-project-S6DXJONQ.js +41 -0
  297. package/dist/xdo-CxDr2Q1C.d.ts +1371 -0
  298. package/guides/README.md +33 -0
  299. package/guides/authoring.md +921 -0
  300. package/guides/cli.md +401 -0
  301. package/guides/codegen.md +177 -0
  302. package/guides/codes.md +404 -0
  303. package/guides/coverage.md +74 -0
  304. package/guides/deploying.md +1525 -0
  305. package/guides/environment.md +400 -0
  306. package/guides/marketplace.md +415 -0
  307. package/guides/module-contract.md +558 -0
  308. package/guides/object-kinds.md +433 -0
  309. package/guides/project-structure.md +43 -0
  310. package/guides/scaffold.md +351 -0
  311. package/guides/typed-frontend.md +242 -0
  312. package/llms/client.md +63 -0
  313. package/llms/errors.md +56 -0
  314. package/llms/fields.md +176 -0
  315. package/llms/filters.md +183 -0
  316. package/llms/kinds-agent-mcp.md +30 -0
  317. package/llms/kinds-core.md +100 -0
  318. package/llms/kinds-knowledge.md +20 -0
  319. package/llms/kinds-realtime.md +76 -0
  320. package/llms/lambda.md +94 -0
  321. package/llms/legacy.md +44 -0
  322. package/llms/lock.md +40 -0
  323. package/llms/object-kinds.md +34 -0
  324. package/llms/statements-calls.md +62 -0
  325. package/llms/statements-catalog.md +320 -0
  326. package/llms/statements-data.md +66 -0
  327. package/llms/statements-runtime.md +68 -0
  328. package/llms/tests.md +59 -0
  329. package/llms/toolchain.md +16 -0
  330. package/llms/triggers.md +36 -0
  331. package/llms/values.md +29 -0
  332. package/llms-full.txt +2085 -0
  333. package/llms.txt +493 -0
  334. package/manifest.json +11224 -0
  335. package/package.json +162 -0
  336. package/types/lambda-globals.d.ts +129 -0
@@ -0,0 +1,558 @@
1
+ # The module contract
2
+
3
+ How to build a Xano SDK add-on: the manifest fields both kinds of module declare, the `@xano/sdk/plugin` types a toolchain module compiles against, every hook and when it fires, and the shapes the SDK refuses rather than absorbs.
4
+
5
+ An add-on is an ordinary npm package. It becomes a Xano SDK module by describing itself in a
6
+ `"xanosdk"` field in its own `package.json`, which is the only description of a package that
7
+ ships with the package. Everything below is that field plus, for a toolchain module, the
8
+ default export of one file.
9
+
10
+ Read [The marketplace](marketplace.md) first for the user-facing side: what `install`,
11
+ `reinstall` and `remove` do to a project, and what your module's settings look like once they
12
+ land in it.
13
+
14
+ ## Which kind are you writing?
15
+
16
+ | | Workspace module | Toolchain module |
17
+ |---|---|---|
18
+ | Extends | the deployed workspace | the CLI |
19
+ | Declares | `"register"`, `"returns"`, `"options"` | `"kind": "toolchain"`, `"plugin"` |
20
+ | Ships | tables, endpoints, functions, agents | questions, file contributions, hooks |
21
+ | Wired by | an import and a call in `xano/index.ts` | nothing: it is configured, not registered |
22
+ | Examples | `@xano-sdk/auth`, `@xano-sdk/vector` | `@xano-sdk/xanoscript` |
23
+
24
+ The `kind` field is what routes a package to one loader or the other. It defaults to
25
+ `workspace`, because every module published before the toolchain kind existed declares none
26
+ and defaulting the other way would strand them. A `kind` the SDK does not recognize is never
27
+ defaulted to one kind or the other: the failure a default would produce is a module reported
28
+ as unwired when its only fault is a typo in one string.
29
+
30
+ What that costs you depends on where the typo is read. `xanosdk init` refuses by name, with
31
+ the valid kinds listed, because a scaffold that silently drops your module is worse than one
32
+ that stops. Every other path, the marketplace verbs and the CLI's own module loading
33
+ included, warns and reads the package as an ordinary dependency rather than taking a whole
34
+ command down over one unrecognized string. Either way it is reported: nothing guesses which
35
+ loader you meant.
36
+
37
+ ## Workspace modules
38
+
39
+ A workspace module exports a `register*` function that mutates a workspace and is called from
40
+ the user's `xano/index.ts`. It says how in its manifest:
41
+
42
+ ```json
43
+ {
44
+ "xanosdk": {
45
+ "register": "registerAuth",
46
+ "returns": "workspace",
47
+ "options": { "canonical": { "value": "authn" } }
48
+ }
49
+ }
50
+ ```
51
+
52
+ | Field | Meaning |
53
+ |---|---|
54
+ | `register` | The exported function to call. Required for the declarative path. |
55
+ | `returns` | `"workspace"` or `"handle"`. Decides whether the call's result is bound to a name. |
56
+ | `options` | What the call's second argument needs, by option name. |
57
+
58
+ `options` entries take one of two forms. A **literal** is written into the generated file as
59
+ JSON, so nothing is imported for it:
60
+
61
+ ```json
62
+ { "canonical": { "value": "authn" } }
63
+ ```
64
+
65
+ An **import** names another package's export, for a module that cannot be registered bare:
66
+
67
+ ```json
68
+ { "authTable": { "package": "@xano-sdk/auth", "export": "userTable" } }
69
+ ```
70
+
71
+ ### Declaring `options` is all or nothing
72
+
73
+ The field states what the **whole** argument is, and is taken at its word without your
74
+ register function being read. Declare `"options": {}` and the call is written bare.
75
+
76
+ **Omit the key and your module has said nothing either way**, so the SDK imports your entry
77
+ and measures `register`'s arity. A function that takes a second parameter nobody declared is
78
+ left **out** of the generated `xano/index.ts` rather than called without it, with a commented
79
+ call and a pointer to `xanosdk marketplace details <pkg> --prompt`. The call would not compile,
80
+ and a fresh scaffold that passes `npm run typecheck` is worth more than one that has your
81
+ module in it.
82
+
83
+ So: a module that needs an option only the user can answer (a URL into their own frontend,
84
+ their own role model) omits the key rather than declaring the rest. That is what lets the CLI
85
+ find the argument and leave the module out honestly.
86
+
87
+ Arity is a signal rather than a proof, because the published artifact is not the source.
88
+ `options?: Opts` and a default parameter downleveled to ES5 both report 2, and
89
+ `(xano, ...rest)` reports 1. Declaring the field is what settles it; nothing measured from
90
+ outside your module can.
91
+
92
+ ### Registration must be a mutation
93
+
94
+ The generated file writes `registerX(app);` as a statement and default-exports `app`, in any
95
+ order. That is only correct because registration **mutates** the workspace it is handed.
96
+ Write your register function so it registers onto the instance it receives; `returns` only
97
+ decides whether the result is worth binding to a name, and is never load-bearing for
98
+ correctness.
99
+
100
+ ### If you declare nothing
101
+
102
+ A module with no `"xanosdk"` field still works. Its installed entry is imported and searched
103
+ for a single export matching `/^register[A-Z]/`. Real exports are read, not prose, so nothing
104
+ can mistake a comment for code. Two matching exports is a refusal rather than a guess, and
105
+ the refusal says the manifest field is what settles it.
106
+
107
+ ### Point somewhere useful
108
+
109
+ Declare a `homepage` in your `package.json`. It is where a refusal sends the reader when the
110
+ CLI cannot wire your module, and the installed manifest is the one place that says where
111
+ without the CLI knowing your package.
112
+
113
+ ## Toolchain modules
114
+
115
+ A toolchain module declares its kind and the file whose default export is the plugin:
116
+
117
+ ```json
118
+ {
119
+ "name": "@acme/reviewable",
120
+ "version": "1.0.0",
121
+ "xanosdk": { "kind": "toolchain", "plugin": "./dist/plugin.js" },
122
+ "peerDependencies": { "@xano/sdk": ">=1.0.0 <2.0.0" }
123
+ }
124
+ ```
125
+
126
+ `@xano/sdk` is a **peer**, never a dependency. Your hooks are handed real compiled data, so
127
+ your module is only correct against the SDK versions whose shapes it understands. Write that
128
+ range out with a floor and a ceiling, as above: the ceiling is the next major, so a module
129
+ built for 1.x loads on every 1.x SDK and is refused on 2.0.
130
+
131
+ That range is **enforced at load**, on every command that discovers modules. An SDK outside it
132
+ is refused by name before your plugin file is imported, naming the range you asked for and the
133
+ version it got. So the floor is the version whose shapes your hooks understand, not an
134
+ aspiration — declare it accurately and the SDK will never quietly run your module against a
135
+ contract it was not built for. Ranges are read as `>=` / `>` / `<=` / `<` / `=` comparators,
136
+ `^`, `~`, exact pins and `||` unions of those; a shape outside that subset is not refused, it
137
+ is simply not checked.
138
+
139
+ ### The import erases
140
+
141
+ ```ts
142
+ import type { ToolchainPlugin, BundleContext } from "@xano/sdk/plugin";
143
+ ```
144
+
145
+ `@xano/sdk/plugin` is a **types-only** entry. Nothing in it has a runtime representation, so
146
+ the import erases at compile time and your published bundle neither carries the SDK nor
147
+ resolves it at runtime. That matters because the SDK is the thing importing your plugin: a
148
+ plugin that pulled the SDK back in would load a second copy of the compiler into the process
149
+ already running one.
150
+
151
+ A plugin that wants the precise per-kind payload shapes can depend on `@xano/sdk/internal`
152
+ for them.
153
+
154
+ ### The plugin
155
+
156
+ ```ts
157
+ import type { ToolchainPlugin } from "@xano/sdk/plugin";
158
+
159
+ const plugin: ToolchainPlugin = {
160
+ kind: "toolchain",
161
+ questions: [...],
162
+ contributes: (answers) => ({ ... }),
163
+ answersFromConfig: (config) => ({ ... }),
164
+ onBundle: async (ctx) => ({ ... }),
165
+ onPreflight: async (ctx) => ({ ... }),
166
+ };
167
+
168
+ export default plugin;
169
+ ```
170
+
171
+ `kind: "toolchain"` is repeated from the manifest so the two must agree. A plugin whose
172
+ default export does not say it is refused: the mismatch means one of the two is stale, and
173
+ guessing which would run code the manifest did not describe.
174
+
175
+ Every hook is optional. A module that only adds a `.gitattributes` line declares
176
+ `contributes` alone.
177
+
178
+ Five hooks, and deliberately no more. This contract was designed from one example, and a
179
+ contract designed from one example generalizes badly, so it covers exactly what that first
180
+ module needed and stops. The second toolchain module is the one that should widen it, with a
181
+ real second set of requirements in hand.
182
+
183
+ ## Questions
184
+
185
+ A module declares questions as **data**, never as a prompt callback:
186
+
187
+ ```ts
188
+ questions: [
189
+ {
190
+ id: "xanoscript",
191
+ label: "Render the compiled workspace to a committed tree",
192
+ type: "boolean",
193
+ default: true,
194
+ },
195
+ {
196
+ id: "dir",
197
+ label: "Directory to render into",
198
+ type: "string",
199
+ default: "xanoscript",
200
+ flag: "xanoscript-dir",
201
+ when: (answers) => answers.xanoscript === true,
202
+ },
203
+ ]
204
+ ```
205
+
206
+ | Field | Meaning |
207
+ |---|---|
208
+ | `id` | Stable key the answer is stored under. |
209
+ | `label` | One line shown to a human. No trailing punctuation. |
210
+ | `type` | `"boolean"`, `"string"` or `"choice"`. |
211
+ | `choices` | The allowed values, for `type: "choice"`. |
212
+ | `default` | Used when the question is not asked and nothing is stored. |
213
+ | `flag` | The long flag that answers it, without dashes. Derived from `id` when omitted. |
214
+ | `when` | Whether to ask at all, given the answers so far. |
215
+
216
+ Two reasons the callback form does not exist, and both are hard. A callback would let a
217
+ module hang a command on a TTY read in CI, where there is no one to answer it, and the SDK
218
+ could not even impose a timeout because it cannot tell a slow prompt from a slow computation.
219
+ And `xanosdk init --web` renders the questionnaire as a browser form, which can display a list
220
+ of choices but cannot run your callback at all. Declaring the question as data means the SDK
221
+ owns every way of answering it.
222
+
223
+ ### Precedence
224
+
225
+ ```
226
+ flag > the project's stored answer > your declared default
227
+ ```
228
+
229
+ The middle rank is what makes a re-ask safe. A stored answer is both the prompt's default and
230
+ the non-interactive fallback, so re-running the questionnaire never quietly resets a
231
+ directory the user chose back to what you shipped. The machine output records which rank each
232
+ answer came from: `flag`, `prompt`, `config`, or `default`.
233
+
234
+ ### The derived flag
235
+
236
+ Each question gets a long flag, from `flag` when you name one and from `id` otherwise.
237
+ Booleans get a `--no-` counterpart, because a question whose default is `true` is otherwise
238
+ unanswerable without a TTY. A `string` or `choice` question is answered with `--flag=value`
239
+ and never `--flag value`: your module is not loaded when the arguments are parsed, so the CLI
240
+ cannot know your flag takes a value and never consumes the following token.
241
+
242
+ **A collision is refused, not absorbed.** A flag matching one the CLI already owns
243
+ (`--name`, `--force`, `--framework`) would never reach you: the parser binds the real flag
244
+ first, so the SDK acts on a value meant for your module and your module quietly records its
245
+ default. Two wrong values, no message. Two modules deriving the same flag fail the same way
246
+ between them. Both are refused by name, naming your module and the command that hit it.
247
+
248
+ Declare an explicit `flag` when the derived name would read badly across several modules
249
+ (`--reviewable-dir` rather than `--dir`) or would collide.
250
+
251
+ ### `when`, and what happens to a stale answer
252
+
253
+ A question `when` excludes is not asked **and no answer is recorded for it**. An unasked
254
+ question has no answer, and recording one would make a later `deploy` act on a choice nobody
255
+ made. On a re-ask that also deletes a stale key a previous run stored for it, and the
256
+ deletion is reported:
257
+
258
+ ```
259
+ ! @acme/reviewable: `dir` is no longer asked, so its stored setting was dropped.
260
+ ```
261
+
262
+ A stored value the question no longer accepts (a choice you retired in an upgrade) is
263
+ replaced by your default and reported, rather than offered back as the prompt default.
264
+
265
+ `when` is evaluated against the answers so far, so a question can depend on one asked before
266
+ it in your own list.
267
+
268
+ ## `contributes`
269
+
270
+ ```ts
271
+ contributes: (answers) => ({
272
+ gitattributes: answers.xanoscript ? [`${answers.dir}/** linguist-generated`] : [],
273
+ config: { enabled: answers.xanoscript === true, dir: answers.dir },
274
+ }),
275
+ ```
276
+
277
+ `contributes` is a **pure function from answers to contributions**. It has no knowledge of
278
+ which command is running and no access to the filesystem, and it may be called several times
279
+ in one run and must return the same thing for the same answers.
280
+
281
+ That is what makes the reconcile idempotent. A module is configured whenever its project is
282
+ reconciled, and `xanosdk init` is only the first of those moments: installing into a project
283
+ that already exists, re-answering later, and removing all run the same pass. The correct
284
+ state is a function of the project's dependencies and its stored answers, so it can be
285
+ rebuilt from scratch at any moment, however your module arrived.
286
+
287
+ `contributes` is **synchronous**. An `async contributes` returns a Promise, which is a
288
+ refusal: a pending Promise has no `gitattributes` and no `config`, which would read as the
289
+ empty contribution that switches your module off.
290
+
291
+ ### The two slots
292
+
293
+ | Slot | What it is |
294
+ |---|---|
295
+ | `gitattributes` | Lines added to the project's `.gitattributes`, in your own marked block. One line per entry, no trailing newline. |
296
+ | `config` | Your module's block in the project's `package.json` `"xanosdk"` object, stored under your package name. |
297
+
298
+ Contributions are **additive against named slots the SDK owns**. You return lines and a
299
+ config object; the SDK composes them into the files it maintains. You never hand back a whole
300
+ file, never rewrite a script string, and never touch the disk yourself. That constraint is
301
+ what makes two installed modules safe together, and it moves the failure earlier: a malformed
302
+ contribution is refused where it was written rather than producing a broken file the user
303
+ meets later.
304
+
305
+ Your lines land in a block naming your package and stamped with your version:
306
+
307
+ ```
308
+ # xanosdk:begin @acme/reviewable
309
+ # xanosdk 1.0.0 — generated; edits inside this block are overwritten
310
+ rendered/** linguist-generated
311
+ # xanosdk:end @acme/reviewable
312
+ ```
313
+
314
+ **An empty contribution is meaningful.** Returning no `gitattributes` lines removes your
315
+ block rather than leaving an empty one, which is how a module whose answers turned it off
316
+ stops applying rules the user declined. Returning nothing at all is the SDK's signal that
317
+ your module reported itself switched off, and it is recorded as `{ "enabled": false }`.
318
+
319
+ ### What you may not contribute
320
+
321
+ A `.gitattributes` rule is refused when it:
322
+
323
+ - **matches `*`.** That applies to every path in the repository, and a module able to set it
324
+ from an add-on install could renormalize line endings repo-wide on the next checkout.
325
+ - **sets `text` or `eol`, including a bare `-text`.** `.gitattributes` is last-match-wins, so
326
+ an appended `* -text` or `*.gen eol=crlf` silently defeats the `* text=auto eol=lf` rule the
327
+ scaffold sets so a byte comparison cannot fail over a contributor's `core.autocrlf`.
328
+ - **spells a xanosdk block marker.** Blocks are located by counting markers, so one forged
329
+ line leaves a file no run can balance, including another package's block.
330
+
331
+ Contribute display rules (`linguist-*`, `diff`) for the paths you own instead. Each check runs
332
+ **per physical line**, because the composer splits every entry on newlines before writing it:
333
+ `["docs/*.md linguist-documentation\n* binary"]` would otherwise pass and then land `* binary`
334
+ on every path in the repository.
335
+
336
+ A `config` that is not a plain object is refused too. The reconciler writes it into the
337
+ `"xanosdk"` block, and the reader refuses any entry that is not an object, so one run would
338
+ author a manifest that every later reconcile then refuses to read. It must also survive
339
+ `JSON.stringify`, so no BigInt and no cycles.
340
+
341
+ ## `answersFromConfig`
342
+
343
+ ```ts
344
+ answersFromConfig: (config) => ({
345
+ xanoscript: config.enabled === true,
346
+ dir: typeof config.dir === "string" ? config.dir : "xanoscript",
347
+ }),
348
+ ```
349
+
350
+ The inverse of `contributes`: read your stored config block back into the answers that
351
+ produced it.
352
+
353
+ `contributes` is a **module-private transform**. Only you know that `{ enabled: true, dir:
354
+ "xanoscript" }` came from `{ xanoscript: true, dir: "xanoscript" }`. The SDK will not guess,
355
+ and matching question ids to same-named config keys would be a convention this contract does
356
+ not state.
357
+
358
+ **Declare it whenever your config keys are not your question ids.** Without it, a re-ask
359
+ offers your shipped defaults and, non-interactively, answers with them, silently resetting a
360
+ directory the user chose. A module that omits it is left as it is rather than re-derived, the
361
+ run reports it as carried, and `reinstall` tells the user what happened before the prompts
362
+ start.
363
+
364
+ It is the one hook that can never take a command down. Keys matching no declared question are
365
+ ignored, and a throw is reported and degrades to your defaults.
366
+
367
+ ## `onBundle`
368
+
369
+ ```ts
370
+ onBundle: async (ctx) => {
371
+ if (ctx.entry === undefined) return { message: "no entry, nothing rendered" };
372
+ if (ctx.frozen) {
373
+ const stale = compare(render(ctx.bundle.payload), readFromDisk(ctx.cwd));
374
+ return stale.length === 0
375
+ ? { message: "rendered tree is current" }
376
+ : { message: "rendered tree is stale", warnings: stale, failed: true };
377
+ }
378
+ write(ctx.cwd, render(ctx.bundle.payload));
379
+ return { message: "rendered tree written" };
380
+ },
381
+ ```
382
+
383
+ Fires on `export` and every `deploy` (`--to` included), **after the bundle compiles and before the network call**.
384
+ That placement is deliberate: a tree describes the **source**, so writing it before the deploy
385
+ means a failed deploy still leaves a correct tree, and a tree never claims something shipped
386
+ that did not.
387
+
388
+ Plugins run in sequence rather than concurrently. They write to the user's tree and to the
389
+ terminal, and interleaved output from two modules writing adjacent directories is harder to
390
+ read than it is slow.
391
+
392
+ | `BundleContext` | What it carries |
393
+ |---|---|
394
+ | `bundle.payload` | The compiled payload, keyed by payload key (`table`, `query`, `app`, ...). |
395
+ | `entry` | The entry file the bundle came from. **Absent** on the `--bundle <path>` branch. |
396
+ | `cwd` | The project root every relative path resolves against. |
397
+ | `command` | `"export"` or `"deploy"`. |
398
+ | `frozen` | The run is `--frozen-lock` (or `export --check` / `deploy --to … --dry-run`, which imply it): a verification, not a write. |
399
+ | `sdkVersion` | The running `@xano/sdk` version, for your own peer-range check. |
400
+ | `config` | Your block from the project's `package.json` `"xanosdk"` object. |
401
+
402
+ **`entry` is optional rather than a made-up path** so a plugin may decline the
403
+ `--bundle <path>` branch, where the input is already-serialized text with no entry and no
404
+ registry behind it. A tree rendered from a bundle the project did not compile describes
405
+ something other than the source beside it.
406
+
407
+ **Under `frozen` a plugin must compare and report, never write.** It is also the mode in which
408
+ a plugin failing to load is fatal rather than a warning.
409
+
410
+ ### Env values are blanked before you see them
411
+
412
+ `payload.env` entries keep their `name` and carry `value: ""`. A hook's output is typically
413
+ committed, and the real values come from a gitignored file, so handing them to a renderer
414
+ would be handing them to git. Your obvious implementation, rendering the payload faithfully,
415
+ would put production credentials into a repository and nothing in the contract would have
416
+ warned you. The declarations survive because they are structure; the secrets do not.
417
+
418
+ ## `onPreflight`
419
+
420
+ ```ts
421
+ onPreflight: async (ctx) => {
422
+ if (ctx.engineRendering.kind === "error") {
423
+ return { message: `could not read the engine's rendering: ${ctx.engineRendering.why}` };
424
+ }
425
+ ...
426
+ },
427
+ ```
428
+
429
+ Fires on `xanosdk preflight`, contributing a comparison to the report.
430
+
431
+ | `PreflightContext` | What it carries |
432
+ |---|---|
433
+ | `engineRendering` | The engine's own rendering of what it has, **or why it could not be had**. |
434
+ | `exportedPayload` | The payload as the engine exported it after import. |
435
+ | `remappedPayload` | The compiled payload with its guids translated to the engine's re-minted ones. |
436
+ | `verbose` | The command was run with `--verbose`: report at length. |
437
+ | `cwd`, `sdkVersion`, `config` | As on `BundleContext`. |
438
+
439
+ The SDK owns the fetch and nothing else. Every derivation past that point is yours: you have
440
+ both payloads, so you render both local sides and compute your own exclusions, and no
441
+ rendering knowledge stays behind in the SDK.
442
+
443
+ `engineRendering` is tagged because the difference is load-bearing. **A missing text route
444
+ must report as unavailable, never as drift.** Treating a failed fetch as an empty rendering
445
+ would diff every object in the workspace against nothing and report a catastrophic mismatch
446
+ that is really a network error.
447
+
448
+ Both payloads are blanked identically, so a comparison between them is unaffected. A
449
+ comparison against `engineRendering` should exclude env values, which the engine has no
450
+ reason to blank.
451
+
452
+ A project whose plugins declare no `onPreflight` never pays for the engine's rendering at all,
453
+ so declaring the hook is what turns that fetch on.
454
+
455
+ ## `HookResult`: `failed` is not a throw
456
+
457
+ ```ts
458
+ interface HookResult {
459
+ message?: string; // one line for the command's normal output
460
+ warnings?: string[]; // per-item notes shown under the message
461
+ failed?: boolean; // the check this hook performed did not pass
462
+ }
463
+ ```
464
+
465
+ This distinction is the whole reason the field exists.
466
+
467
+ - **A thrown error is a broken plugin.** It could not do its job. On `export` and `deploy` the
468
+ command warns and carries on, and under `--frozen-lock` it is fatal: a check that did not
469
+ run must not read as a check that passed. A throw from `onPreflight` is never fatal, because
470
+ `preflight` is itself a check and its own report is worth printing, but it does fail the
471
+ gate for the same reason.
472
+ - **`failed: true` is a working plugin reporting that what it checked does not pass.** A
473
+ committed tree that no longer matches the source, say. It is never swallowed and reaches the
474
+ command's exit code on every run.
475
+
476
+ Collapsing the two would break both directions: a bug in a plugin would fail a deploy that is
477
+ fine, and a real staleness finding would be swallowed as a plugin bug. `preflight` maps
478
+ `failed` onto the same non-zero exit code it uses for its own findings, which is what lets CI
479
+ keep gating on it.
480
+
481
+ Set `failed` only when your plugin ran correctly and the answer is no.
482
+
483
+ ## Contract skew is refused, never absorbed
484
+
485
+ Because every hook is optional, a module written against a shape of this contract the running
486
+ SDK does not have would load, register, and contribute **nothing**, silently, since "a module
487
+ with no contributions" is a legitimate outcome. So the SDK detects each known skew structurally
488
+ and refuses it by name, with the upgrade to make.
489
+
490
+ | Shape | Why it is refused | The fix |
491
+ |---|---|---|
492
+ | A `files` hook and no `contributes` | `files` was renamed once the hook stopped running only at scaffold time. | Rename it. `contributes(answers)` returns a `ProjectContributions`. |
493
+ | A `ciSteps` entry in what `contributes` returns | The slot is gone. Your check already rides on `xanosdk export ./xano/index.ts --frozen-lock` through `onBundle` with `frozen` set. | Drop it. No workflow step of your own is needed. |
494
+ | `contributes` returning a string, a number, an array or a Promise | Indistinguishable from "contributed nothing", which is recorded as switched off. | Return an object, or nothing at all. `contributes` is synchronous. |
495
+ | A default export whose `kind` is not `"toolchain"` | The manifest and the module disagree, so one of the two is stale. | Make them agree. |
496
+ | An SDK outside your declared `peerDependencies["@xano/sdk"]` range | Your hooks were written against shapes this SDK does not have, so it would apply nothing. | Upgrade the SDK, or install a build of your module made for it. |
497
+
498
+ The peer-range row is the one that catches the skew **in the other direction** — your module
499
+ newer than the SDK running it. Nothing about the plugin object can reveal that: the check that
500
+ would recognize a hook name you have not yet invented would have to ship in the SDK that
501
+ predates it. Your declared range is the only fact both sides hold, which is why it is read
502
+ rather than trusted to npm's install-time warning — that warning is routinely bypassed by
503
+ `--legacy-peer-deps`, by pnpm and yarn peer handling, and by an SDK downgraded after the
504
+ install. It is also the only version check that reaches a **reconcile**: `sdkVersion` is handed
505
+ to `onBundle` and `onPreflight` only, so a guard of your own inside a hook first fires on the
506
+ next `export` or `deploy`, well after the `marketplace install` that quietly wrote nothing.
507
+
508
+ Someone pinned to an older SDK is never locked out by this: `"enabled": false` is read before
509
+ the check runs, and `marketplace remove` uninstalls before it reconciles.
510
+
511
+ The installers ask the same question, so a module of yours that cannot run on the SDK in front
512
+ of it never gets left on disk waiting to be refused: `marketplace install` undoes an install it
513
+ just made, `reinstall` refuses without touching the module you already had, and
514
+ `init --marketplace` reports it and takes it back off. See
515
+ [Peer ranges](marketplace.md#peer-ranges).
516
+
517
+ The last row in the table above is the pattern: silently dropping a contributed CI step would
518
+ be a guard that stops running, and a wrong return type would switch a working module off and
519
+ keep it off while `marketplace remove` refused it for being deliberately disabled. Neither
520
+ failure would ever reach you.
521
+
522
+ ## What a throwing hook costs
523
+
524
+ A module that cannot describe its contributions loses its contributions, not the command.
525
+ `contributes` is third-party code running after an install has already written to the target
526
+ directory; letting it take the run down would leave a half-populated project every re-run then
527
+ refuses. Your module is reported and every byte it owns is left exactly as it was, including
528
+ its config, whose presence is what tells the next run the questionnaire already completed.
529
+
530
+ A **refusal** (the rows in the table above) does propagate. Those are deterministic, so
531
+ re-running cannot fix them, and carrying on would ship a project that does not do what your
532
+ module says it does.
533
+
534
+ ## Testing your module
535
+
536
+ The SDK's own suite drives a generated fixture plugin through the real load path rather than
537
+ mocking it, which is the shape worth copying: write your plugin into a temp project's
538
+ `node_modules` with a real `package.json`, and run the CLI against it.
539
+
540
+ Things worth proving about your own module:
541
+
542
+ - `contributes` returns the same thing for the same answers, called twice.
543
+ - `answersFromConfig(contributes(a).config)` round-trips back to `a` for every answer set.
544
+ - Your `onBundle` writes nothing when `ctx.frozen` is true.
545
+ - Your `onBundle` handles `ctx.entry === undefined`.
546
+ - Your hooks never read `payload.env` values, because they are blanked.
547
+
548
+ ## Publishing
549
+
550
+ Publish to npm like any package. To appear in `xanosdk marketplace list` and `search`, the
551
+ module also needs a catalogue entry, which carries its title, tagline, description, tags, the
552
+ objects it installs, what the user supplies, the registration snippet, and the agent prompt
553
+ `details --prompt` emits. Write that prompt for a machine: it is what `init --marketplace`
554
+ and `marketplace install` point at whenever they cannot wire your module themselves.
555
+
556
+ Nothing about the marketplace is required to use a module. Any package declaring a `"xanosdk"`
557
+ field works from a `file:` path, a tarball, a git spec or a private registry, and the CLI
558
+ passes the specifier to npm exactly as typed.