kiriya 0.1.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 (282) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/dist/src/config/clean-rules.js +39 -0
  5. package/dist/src/config/config-keys.js +24 -0
  6. package/dist/src/config/dependency-directories.js +16 -0
  7. package/dist/src/config/modules.js +38 -0
  8. package/dist/src/config/protected-paths.js +32 -0
  9. package/dist/src/config/rebuildable-directories.js +20 -0
  10. package/dist/src/core/application/command-registry.js +57 -0
  11. package/dist/src/core/application/config-values.js +33 -0
  12. package/dist/src/core/application/path-guard.js +69 -0
  13. package/dist/src/core/application/paths.js +77 -0
  14. package/dist/src/core/application/plugin-loader.js +100 -0
  15. package/dist/src/core/application/process-ending.js +28 -0
  16. package/dist/src/core/application/root-scope.js +123 -0
  17. package/dist/src/core/application/safety.js +26 -0
  18. package/dist/src/core/application/text-input.js +44 -0
  19. package/dist/src/core/application/walk.js +34 -0
  20. package/dist/src/core/domain/command.js +8 -0
  21. package/dist/src/core/domain/encodings.js +70 -0
  22. package/dist/src/core/domain/errors.js +49 -0
  23. package/dist/src/core/domain/glob.js +57 -0
  24. package/dist/src/core/domain/global-options.js +10 -0
  25. package/dist/src/core/domain/input-schema.js +54 -0
  26. package/dist/src/core/domain/message.js +7 -0
  27. package/dist/src/core/domain/module.js +2 -0
  28. package/dist/src/core/domain/names.js +22 -0
  29. package/dist/src/core/domain/ports/clipboard.js +2 -0
  30. package/dist/src/core/domain/ports/clock.js +2 -0
  31. package/dist/src/core/domain/ports/command-catalog.js +2 -0
  32. package/dist/src/core/domain/ports/compression.js +2 -0
  33. package/dist/src/core/domain/ports/config-store.js +2 -0
  34. package/dist/src/core/domain/ports/confirmation.js +2 -0
  35. package/dist/src/core/domain/ports/environment.js +2 -0
  36. package/dist/src/core/domain/ports/file-content.js +2 -0
  37. package/dist/src/core/domain/ports/file-system.js +2 -0
  38. package/dist/src/core/domain/ports/hasher.js +2 -0
  39. package/dist/src/core/domain/ports/network.js +2 -0
  40. package/dist/src/core/domain/ports/opener.js +2 -0
  41. package/dist/src/core/domain/ports/passthrough.js +2 -0
  42. package/dist/src/core/domain/ports/plugin-inventory.js +2 -0
  43. package/dist/src/core/domain/ports/plugin-source.js +2 -0
  44. package/dist/src/core/domain/ports/port-table.js +2 -0
  45. package/dist/src/core/domain/ports/process-runner.js +2 -0
  46. package/dist/src/core/domain/ports/process-table.js +2 -0
  47. package/dist/src/core/domain/ports/protected-paths.js +2 -0
  48. package/dist/src/core/domain/ports/random-source.js +2 -0
  49. package/dist/src/core/domain/ports/standard-input.js +2 -0
  50. package/dist/src/core/domain/ports/system-info.js +2 -0
  51. package/dist/src/core/domain/ports/trash.js +2 -0
  52. package/dist/src/core/domain/secrets.js +59 -0
  53. package/dist/src/core/domain/targets.js +41 -0
  54. package/dist/src/core/domain/text-case.js +39 -0
  55. package/dist/src/core/domain/values/bytes.js +13 -0
  56. package/dist/src/core/domain/view.js +2 -0
  57. package/dist/src/core/infrastructure/node/can-listen.js +10 -0
  58. package/dist/src/core/infrastructure/node/closed-standard-input.adapter.js +12 -0
  59. package/dist/src/core/infrastructure/node/config-location.js +27 -0
  60. package/dist/src/core/infrastructure/node/fs-errors.js +22 -0
  61. package/dist/src/core/infrastructure/node/json-config-store.adapter.js +69 -0
  62. package/dist/src/core/infrastructure/node/node-compression.adapter.js +82 -0
  63. package/dist/src/core/infrastructure/node/node-environment.adapter.js +21 -0
  64. package/dist/src/core/infrastructure/node/node-file-content.adapter.js +99 -0
  65. package/dist/src/core/infrastructure/node/node-file-system.adapter.js +169 -0
  66. package/dist/src/core/infrastructure/node/node-hasher.adapter.js +32 -0
  67. package/dist/src/core/infrastructure/node/node-network.adapter.js +168 -0
  68. package/dist/src/core/infrastructure/node/node-plugin-source.adapter.js +89 -0
  69. package/dist/src/core/infrastructure/node/node-process-runner.adapter.js +48 -0
  70. package/dist/src/core/infrastructure/node/node-random-source.adapter.js +7 -0
  71. package/dist/src/core/infrastructure/node/node-standard-input.adapter.js +24 -0
  72. package/dist/src/core/infrastructure/node/node-system-info.adapter.js +63 -0
  73. package/dist/src/core/infrastructure/node/process-signals.js +51 -0
  74. package/dist/src/core/infrastructure/node/run-program.js +65 -0
  75. package/dist/src/core/infrastructure/node/system-clock.adapter.js +22 -0
  76. package/dist/src/core/infrastructure/node/typed-programs.js +45 -0
  77. package/dist/src/core/infrastructure/platform/linux/freedesktop-trash.adapter.js +115 -0
  78. package/dist/src/core/infrastructure/platform/linux/linux-clipboard.adapter.js +70 -0
  79. package/dist/src/core/infrastructure/platform/linux/linux-opener.adapter.js +21 -0
  80. package/dist/src/core/infrastructure/platform/linux/linux-port-table.adapter.js +97 -0
  81. package/dist/src/core/infrastructure/platform/linux/linux-process-table.adapter.js +61 -0
  82. package/dist/src/core/infrastructure/platform/macos/macos-clipboard.adapter.js +29 -0
  83. package/dist/src/core/infrastructure/platform/macos/macos-opener.adapter.js +13 -0
  84. package/dist/src/core/infrastructure/platform/macos/macos-port-table.adapter.js +86 -0
  85. package/dist/src/core/infrastructure/platform/macos/macos-process-table.adapter.js +62 -0
  86. package/dist/src/core/infrastructure/platform/macos/macos-trash.adapter.js +64 -0
  87. package/dist/src/core/infrastructure/platform/windows/system32.js +6 -0
  88. package/dist/src/core/infrastructure/platform/windows/windows-clipboard.adapter.js +50 -0
  89. package/dist/src/core/infrastructure/platform/windows/windows-opener.adapter.js +11 -0
  90. package/dist/src/core/infrastructure/platform/windows/windows-port-table.adapter.js +38 -0
  91. package/dist/src/core/infrastructure/platform/windows/windows-process-table.adapter.js +134 -0
  92. package/dist/src/core/infrastructure/platform/windows/windows-trash.adapter.js +86 -0
  93. package/dist/src/core/presentation/cli/argv.js +83 -0
  94. package/dist/src/core/presentation/cli/cli-application.js +201 -0
  95. package/dist/src/core/presentation/cli/cli-view-format.js +46 -0
  96. package/dist/src/core/presentation/cli/help.js +202 -0
  97. package/dist/src/core/presentation/cli/json-output.js +28 -0
  98. package/dist/src/core/presentation/cli/style.js +21 -0
  99. package/dist/src/core/presentation/cli/suggest.js +25 -0
  100. package/dist/src/core/presentation/cli/terminal-confirmation.js +63 -0
  101. package/dist/src/core/presentation/ended-processes.js +18 -0
  102. package/dist/src/core/presentation/i18n/translator.js +23 -0
  103. package/dist/src/core/presentation/list-preview.js +10 -0
  104. package/dist/src/core/presentation/mcp/elicitation.js +55 -0
  105. package/dist/src/core/presentation/mcp/mcp-application.js +60 -0
  106. package/dist/src/core/presentation/mcp/mcp-confirmation.js +59 -0
  107. package/dist/src/core/presentation/mcp/mcp-server.js +355 -0
  108. package/dist/src/core/presentation/mcp/protocol.js +32 -0
  109. package/dist/src/core/presentation/mcp/request-state.js +66 -0
  110. package/dist/src/core/presentation/mcp/tool-definitions.js +149 -0
  111. package/dist/src/core/presentation/mcp/tool-results.js +56 -0
  112. package/dist/src/i18n/locales/en.js +827 -0
  113. package/dist/src/main.js +154 -0
  114. package/dist/src/modules/archive/application/archive-sources.js +55 -0
  115. package/dist/src/modules/archive/application/create-tar.use-case.js +123 -0
  116. package/dist/src/modules/archive/application/create-zip.use-case.js +106 -0
  117. package/dist/src/modules/archive/application/extract-tar.use-case.js +240 -0
  118. package/dist/src/modules/archive/application/extract-zip.use-case.js +181 -0
  119. package/dist/src/modules/archive/application/tar-reader.js +45 -0
  120. package/dist/src/modules/archive/application/zip-reader.js +57 -0
  121. package/dist/src/modules/archive/archive.module.js +27 -0
  122. package/dist/src/modules/archive/domain/crc32.js +18 -0
  123. package/dist/src/modules/archive/domain/tar-format.js +97 -0
  124. package/dist/src/modules/archive/domain/tar-stream.js +223 -0
  125. package/dist/src/modules/archive/domain/zip-format.js +158 -0
  126. package/dist/src/modules/archive/presentation/tar.view.js +17 -0
  127. package/dist/src/modules/archive/presentation/untar.view.js +30 -0
  128. package/dist/src/modules/archive/presentation/unzip.view.js +24 -0
  129. package/dist/src/modules/archive/presentation/zip.view.js +17 -0
  130. package/dist/src/modules/clip/application/copy-text.use-case.js +43 -0
  131. package/dist/src/modules/clip/application/paste-text.use-case.js +25 -0
  132. package/dist/src/modules/clip/clip.module.js +16 -0
  133. package/dist/src/modules/clip/presentation/clip.views.js +7 -0
  134. package/dist/src/modules/completion/application/print-script.use-case.js +38 -0
  135. package/dist/src/modules/completion/application/suggest-words.use-case.js +37 -0
  136. package/dist/src/modules/completion/completion.module.js +14 -0
  137. package/dist/src/modules/completion/domain/scripts.js +91 -0
  138. package/dist/src/modules/completion/domain/suggest.js +134 -0
  139. package/dist/src/modules/completion/presentation/completion.views.js +6 -0
  140. package/dist/src/modules/config/application/config-commands.js +14 -0
  141. package/dist/src/modules/config/application/get-setting.use-case.js +25 -0
  142. package/dist/src/modules/config/application/list-config-keys.use-case.js +26 -0
  143. package/dist/src/modules/config/application/list-settings.use-case.js +28 -0
  144. package/dist/src/modules/config/application/set-setting.use-case.js +53 -0
  145. package/dist/src/modules/config/application/show-config-path.use-case.js +21 -0
  146. package/dist/src/modules/config/application/unset-setting.use-case.js +28 -0
  147. package/dist/src/modules/config/config.module.js +28 -0
  148. package/dist/src/modules/config/presentation/config.views.js +33 -0
  149. package/dist/src/modules/convert/application/convert-case.use-case.js +38 -0
  150. package/dist/src/modules/convert/application/convert-codecs.use-case.js +92 -0
  151. package/dist/src/modules/convert/application/convert-input.js +16 -0
  152. package/dist/src/modules/convert/application/convert-json.use-case.js +51 -0
  153. package/dist/src/modules/convert/application/convert-time.use-case.js +39 -0
  154. package/dist/src/modules/convert/application/decode-jwt.use-case.js +52 -0
  155. package/dist/src/modules/convert/convert.module.js +29 -0
  156. package/dist/src/modules/convert/domain/json-syntax.js +135 -0
  157. package/dist/src/modules/convert/domain/jwt.js +29 -0
  158. package/dist/src/modules/convert/domain/moments.js +20 -0
  159. package/dist/src/modules/convert/presentation/convert.views.js +44 -0
  160. package/dist/src/modules/docker/application/clean-engine.use-case.js +77 -0
  161. package/dist/src/modules/docker/application/compose-project.js +45 -0
  162. package/dist/src/modules/docker/application/docker-engine.js +40 -0
  163. package/dist/src/modules/docker/application/list-containers.use-case.js +69 -0
  164. package/dist/src/modules/docker/application/run-compose.use-case.js +83 -0
  165. package/dist/src/modules/docker/application/show-logs.use-case.js +51 -0
  166. package/dist/src/modules/docker/application/stop-project.use-case.js +56 -0
  167. package/dist/src/modules/docker/docker.module.js +23 -0
  168. package/dist/src/modules/docker/domain/compose-file.js +105 -0
  169. package/dist/src/modules/docker/domain/engine-output.js +65 -0
  170. package/dist/src/modules/docker/presentation/docker.views.js +63 -0
  171. package/dist/src/modules/doctor/application/run-checks.use-case.js +128 -0
  172. package/dist/src/modules/doctor/doctor.module.js +13 -0
  173. package/dist/src/modules/doctor/domain/versions.js +22 -0
  174. package/dist/src/modules/doctor/presentation/doctor.view.js +30 -0
  175. package/dist/src/modules/env/application/check-dotenv.use-case.js +116 -0
  176. package/dist/src/modules/env/application/inspect-path.use-case.js +71 -0
  177. package/dist/src/modules/env/application/path-entries.js +28 -0
  178. package/dist/src/modules/env/application/show-variables.use-case.js +47 -0
  179. package/dist/src/modules/env/domain/dotenv.js +62 -0
  180. package/dist/src/modules/env/env.module.js +17 -0
  181. package/dist/src/modules/env/presentation/env.views.js +43 -0
  182. package/dist/src/modules/files/application/clean-builds.use-case.js +149 -0
  183. package/dist/src/modules/files/application/compare-paths.use-case.js +127 -0
  184. package/dist/src/modules/files/application/copy-paths.use-case.js +101 -0
  185. package/dist/src/modules/files/application/create-paths.use-case.js +85 -0
  186. package/dist/src/modules/files/application/delete-paths.use-case.js +127 -0
  187. package/dist/src/modules/files/application/find-duplicates.use-case.js +105 -0
  188. package/dist/src/modules/files/application/find-files.use-case.js +131 -0
  189. package/dist/src/modules/files/application/hash-files.use-case.js +73 -0
  190. package/dist/src/modules/files/application/list-entries.use-case.js +87 -0
  191. package/dist/src/modules/files/application/measure-folders.use-case.js +102 -0
  192. package/dist/src/modules/files/application/move-entry.js +19 -0
  193. package/dist/src/modules/files/application/move-paths.use-case.js +82 -0
  194. package/dist/src/modules/files/application/read-text.use-case.js +92 -0
  195. package/dist/src/modules/files/application/rename-paths.use-case.js +184 -0
  196. package/dist/src/modules/files/application/replace-text.use-case.js +146 -0
  197. package/dist/src/modules/files/application/search-text.use-case.js +115 -0
  198. package/dist/src/modules/files/application/show-info.use-case.js +80 -0
  199. package/dist/src/modules/files/application/show-tree.use-case.js +75 -0
  200. package/dist/src/modules/files/application/sync-folders.use-case.js +162 -0
  201. package/dist/src/modules/files/application/text-files.js +46 -0
  202. package/dist/src/modules/files/application/transfers.js +35 -0
  203. package/dist/src/modules/files/domain/case-style.js +18 -0
  204. package/dist/src/modules/files/domain/filters.js +9 -0
  205. package/dist/src/modules/files/domain/sync-plan.js +35 -0
  206. package/dist/src/modules/files/domain/text-encoding.js +75 -0
  207. package/dist/src/modules/files/domain/values.js +28 -0
  208. package/dist/src/modules/files/files.module.js +70 -0
  209. package/dist/src/modules/files/presentation/clean.view.js +15 -0
  210. package/dist/src/modules/files/presentation/compare.view.js +50 -0
  211. package/dist/src/modules/files/presentation/copy.view.js +17 -0
  212. package/dist/src/modules/files/presentation/delete.view.js +16 -0
  213. package/dist/src/modules/files/presentation/dupes.view.js +20 -0
  214. package/dist/src/modules/files/presentation/find.view.js +16 -0
  215. package/dist/src/modules/files/presentation/grep.view.js +27 -0
  216. package/dist/src/modules/files/presentation/hash.view.js +15 -0
  217. package/dist/src/modules/files/presentation/info.view.js +36 -0
  218. package/dist/src/modules/files/presentation/list.view.js +17 -0
  219. package/dist/src/modules/files/presentation/move.view.js +14 -0
  220. package/dist/src/modules/files/presentation/new.view.js +8 -0
  221. package/dist/src/modules/files/presentation/read.view.js +6 -0
  222. package/dist/src/modules/files/presentation/rename.view.js +16 -0
  223. package/dist/src/modules/files/presentation/replace.view.js +26 -0
  224. package/dist/src/modules/files/presentation/size.view.js +24 -0
  225. package/dist/src/modules/files/presentation/sync.view.js +37 -0
  226. package/dist/src/modules/files/presentation/tree.view.js +22 -0
  227. package/dist/src/modules/gen/application/gen-options.js +18 -0
  228. package/dist/src/modules/gen/application/generate-passwords.use-case.js +39 -0
  229. package/dist/src/modules/gen/application/generate-tokens.use-case.js +38 -0
  230. package/dist/src/modules/gen/application/generate-ulids.use-case.js +40 -0
  231. package/dist/src/modules/gen/application/generate-uuids.use-case.js +32 -0
  232. package/dist/src/modules/gen/domain/identifiers.js +65 -0
  233. package/dist/src/modules/gen/domain/secrets.js +43 -0
  234. package/dist/src/modules/gen/gen.module.js +20 -0
  235. package/dist/src/modules/git/application/fetch-repositories.use-case.js +50 -0
  236. package/dist/src/modules/git/application/git-client.js +62 -0
  237. package/dist/src/modules/git/application/pull-repositories.use-case.js +67 -0
  238. package/dist/src/modules/git/application/repositories.js +67 -0
  239. package/dist/src/modules/git/application/show-status.use-case.js +49 -0
  240. package/dist/src/modules/git/application/switch-branch.use-case.js +118 -0
  241. package/dist/src/modules/git/domain/repository-status.js +37 -0
  242. package/dist/src/modules/git/git.module.js +25 -0
  243. package/dist/src/modules/git/presentation/git.views.js +100 -0
  244. package/dist/src/modules/net/application/check-connection.use-case.js +71 -0
  245. package/dist/src/modules/net/application/list-addresses.use-case.js +29 -0
  246. package/dist/src/modules/net/application/lookup-name.use-case.js +55 -0
  247. package/dist/src/modules/net/net.module.js +17 -0
  248. package/dist/src/modules/net/presentation/net.views.js +39 -0
  249. package/dist/src/modules/open/application/open-target.use-case.js +53 -0
  250. package/dist/src/modules/open/domain/targets.js +45 -0
  251. package/dist/src/modules/open/open.module.js +12 -0
  252. package/dist/src/modules/open/presentation/open.view.js +6 -0
  253. package/dist/src/modules/port/application/end-port-owners.use-case.js +68 -0
  254. package/dist/src/modules/port/application/find-free-port.use-case.js +40 -0
  255. package/dist/src/modules/port/application/find-listeners.use-case.js +34 -0
  256. package/dist/src/modules/port/application/port-owners.js +25 -0
  257. package/dist/src/modules/port/domain/port-number.js +8 -0
  258. package/dist/src/modules/port/port.module.js +17 -0
  259. package/dist/src/modules/port/presentation/port.views.js +27 -0
  260. package/dist/src/modules/proc/application/end-processes.use-case.js +77 -0
  261. package/dist/src/modules/proc/application/find-processes.use-case.js +50 -0
  262. package/dist/src/modules/proc/application/list-processes.use-case.js +41 -0
  263. package/dist/src/modules/proc/application/show-tree.use-case.js +40 -0
  264. package/dist/src/modules/proc/domain/process-tree.js +55 -0
  265. package/dist/src/modules/proc/presentation/proc.views.js +28 -0
  266. package/dist/src/modules/proc/proc.module.js +19 -0
  267. package/dist/src/modules/sys/application/create-report.use-case.js +30 -0
  268. package/dist/src/modules/sys/application/find-tools.use-case.js +20 -0
  269. package/dist/src/modules/sys/application/machine-facts.js +44 -0
  270. package/dist/src/modules/sys/application/show-info.use-case.js +29 -0
  271. package/dist/src/modules/sys/domain/tools.js +17 -0
  272. package/dist/src/modules/sys/presentation/sys.views.js +67 -0
  273. package/dist/src/modules/sys/sys.module.js +17 -0
  274. package/dist/src/modules/wait/application/poll.js +26 -0
  275. package/dist/src/modules/wait/application/wait-file.use-case.js +55 -0
  276. package/dist/src/modules/wait/application/wait-port.use-case.js +76 -0
  277. package/dist/src/modules/wait/application/wait-url.use-case.js +71 -0
  278. package/dist/src/modules/wait/domain/reasons.js +14 -0
  279. package/dist/src/modules/wait/domain/wait-input.js +50 -0
  280. package/dist/src/modules/wait/presentation/wait.views.js +31 -0
  281. package/dist/src/modules/wait/wait.module.js +21 -0
  282. package/package.json +57 -0
@@ -0,0 +1,55 @@
1
+ import { isObject } from "./protocol.js";
2
+ /** Whether client capabilities include form mode. An empty `elicitation` object means form mode, as the specification says. */
3
+ export function supportsFormElicitation(capabilities) {
4
+ const elicitation = capabilities["elicitation"];
5
+ if (!isObject(elicitation))
6
+ return false;
7
+ return Object.keys(elicitation).length === 0 || isObject(elicitation["form"]);
8
+ }
9
+ /** An answer as the client sent it. Anything that is not a valid answer counts as cancelled. */
10
+ export function answerOf(value) {
11
+ if (!isObject(value))
12
+ return { action: "cancel", content: {} };
13
+ const { action, content } = value;
14
+ if (action !== "accept" && action !== "decline" && action !== "cancel")
15
+ return { action: "cancel", content: {} };
16
+ const fields = {};
17
+ if (isObject(content)) {
18
+ for (const [name, field] of Object.entries(content))
19
+ if (typeof field === "string")
20
+ fields[name] = field;
21
+ }
22
+ return { action, content: fields };
23
+ }
24
+ /** Ends a round of a call whose result asks for input; the client answers by calling again. */
25
+ export class InputRequired extends Error {
26
+ inputRequests;
27
+ requestState;
28
+ constructor(inputRequests, requestState) {
29
+ super("input required");
30
+ this.inputRequests = inputRequests;
31
+ this.requestState = requestState;
32
+ this.name = "InputRequired";
33
+ }
34
+ }
35
+ /**
36
+ * The 2026-07-28 way to ask: the question travels in the call's result, and the answer
37
+ * comes back when the client calls again, so the command runs again from the start.
38
+ * Questions are numbered in the order the command asks them.
39
+ */
40
+ export class RoundTripElicitation {
41
+ answers;
42
+ stop;
43
+ asked = 0;
44
+ constructor(answers, stop) {
45
+ this.answers = answers;
46
+ this.stop = stop;
47
+ }
48
+ ask(question) {
49
+ this.asked += 1;
50
+ const key = `confirm-${this.asked}`;
51
+ const answer = this.answers[key];
52
+ return answer === undefined ? Promise.reject(this.stop(key, question)) : Promise.resolve(answer);
53
+ }
54
+ }
55
+ //# sourceMappingURL=elicitation.js.map
@@ -0,0 +1,60 @@
1
+ import { createInterface } from "node:readline";
2
+ import { mcpAllowsDestroy, mcpAllowsWrite } from "../../application/config-values.js";
3
+ import { RootScope } from "../../application/root-scope.js";
4
+ import { KiriyaError } from "../../domain/errors.js";
5
+ import { RawReader } from "../../domain/input-schema.js";
6
+ import { message } from "../../domain/message.js";
7
+ import { Translator } from "../i18n/translator.js";
8
+ import { McpServer } from "./mcp-server.js";
9
+ /** What `kiriya mcp` takes on its command line. */
10
+ export const MCP_INPUT = {
11
+ positionals: [{ name: "folders", description: "core.mcp.arg.folders", required: false, variadic: true, path: true }],
12
+ options: {
13
+ root: { type: "string", multiple: true, path: true, description: "core.mcp.option.root", valueName: "<folder>" },
14
+ },
15
+ // Folders as arguments serve an app that passes a list of them, such as the one an MCP bundle starts.
16
+ parse: (raw) => {
17
+ const reader = new RawReader(raw);
18
+ return { roots: [...reader.positionalsFrom(0), ...reader.strings("root")] };
19
+ },
20
+ };
21
+ /**
22
+ * `kiriya mcp`: one JSON-RPC message per line on stdin and stdout until stdin closes.
23
+ * Only protocol messages reach stdout; diagnostics go to stderr. A root that is not a
24
+ * folder stops it before it reads anything. The settings are read once, at the start.
25
+ */
26
+ export async function serveMcp(deps, input, cwd) {
27
+ const translator = new Translator(deps.catalog);
28
+ const log = (value) => {
29
+ deps.stderr.write(`${translator.text(value)}\n`);
30
+ };
31
+ const scope = await RootScope.open(deps.fileSystem, input.roots, cwd, [deps.config.path]);
32
+ // A configuration file that cannot be read allows nothing beyond reading.
33
+ const settings = await deps.config.read().then((values) => ({ allowWrite: mcpAllowsWrite(values), allowDestroy: mcpAllowsDestroy(values) }), (error) => {
34
+ const detail = error instanceof KiriyaError ? translator.text(error.detail) : String(error);
35
+ log(message("core.mcp.config-unreadable", { detail }));
36
+ return { allowWrite: false, allowDestroy: false };
37
+ });
38
+ const server = new McpServer({
39
+ registry: deps.registry,
40
+ translator,
41
+ version: deps.version,
42
+ scope,
43
+ ...settings,
44
+ secret: deps.random.bytes(32),
45
+ now: () => deps.clock.now(),
46
+ send: (value) => deps.stdout.write(`${JSON.stringify(value)}\n`),
47
+ log,
48
+ });
49
+ const lines = createInterface({ input: deps.stdin, crlfDelay: Number.POSITIVE_INFINITY });
50
+ // A client that goes away closes stdout too; there is no one left to answer.
51
+ deps.stdout.once("error", () => lines.close());
52
+ for await (const line of lines) {
53
+ if (line.trim() !== "")
54
+ server.receive(line);
55
+ }
56
+ await server.close();
57
+ await new Promise((resolve) => deps.stdout.write("", () => resolve()));
58
+ return 0;
59
+ }
60
+ //# sourceMappingURL=mcp-application.js.map
@@ -0,0 +1,59 @@
1
+ import { RefusedError } from "../../domain/errors.js";
2
+ import { message } from "../../domain/message.js";
3
+ /**
4
+ * Questions over MCP, by the tool's safety level:
5
+ *
6
+ * - `write`: yes-or-no questions are answered yes by the user's `mcp.allowWrite` setting,
7
+ * as `--yes` would answer them on the command line.
8
+ * - `destroy`: yes-or-no questions are asked through elicitation, so no call of such a
9
+ * tool changes anything the user did not accept.
10
+ * - Typed confirmations, in any tool, are asked through elicitation, which exists only
11
+ * with `mcp.allowDestroy` and a client that supports it; without it they are refused.
12
+ *
13
+ * A value passed as `--confirm` never counts, since an agent could pass it.
14
+ */
15
+ export class McpConfirmation {
16
+ safety;
17
+ elicitation;
18
+ translator;
19
+ constructor(safety, elicitation, translator) {
20
+ this.safety = safety;
21
+ this.elicitation = elicitation;
22
+ this.translator = translator;
23
+ }
24
+ async approve(question) {
25
+ if (this.safety === "read")
26
+ return false;
27
+ if (this.safety === "write")
28
+ return true;
29
+ const answer = await this.asker().ask({
30
+ message: this.translator.text(question),
31
+ requestedSchema: { type: "object", properties: {} },
32
+ });
33
+ return answer.action === "accept";
34
+ }
35
+ async typed(warning, expected) {
36
+ const instruction = this.translator.text(message("core.mcp.confirm.type", { expected }));
37
+ const answer = await this.asker().ask({
38
+ message: `${this.translator.text(warning)} ${instruction}`,
39
+ requestedSchema: {
40
+ type: "object",
41
+ properties: {
42
+ value: {
43
+ type: "string",
44
+ title: this.translator.text(message("core.mcp.confirm.field")),
45
+ description: instruction,
46
+ },
47
+ },
48
+ required: ["value"],
49
+ },
50
+ });
51
+ return answer.action === "accept" && answer.content["value"]?.trim() === expected;
52
+ }
53
+ asker() {
54
+ if (this.elicitation === null)
55
+ throw new RefusedError("core.mcp.cannot-confirm");
56
+ return this.elicitation;
57
+ }
58
+ }
59
+ //# sourceMappingURL=mcp-confirmation.js.map
@@ -0,0 +1,355 @@
1
+ import { pathValues } from "../../application/root-scope.js";
2
+ import { InterruptedError, KiriyaError } from "../../domain/errors.js";
3
+ import { message } from "../../domain/message.js";
4
+ import { byCodePoint } from "../../domain/names.js";
5
+ import { answerOf, InputRequired, RoundTripElicitation, supportsFormElicitation, } from "./elicitation.js";
6
+ import { McpConfirmation } from "./mcp-confirmation.js";
7
+ import { INTERNAL_ERROR, INVALID_PARAMS, INVALID_REQUEST, isObject, LATEST_LEGACY_VERSION, LEGACY_VERSIONS, META_CLIENT_CAPABILITIES, META_PROTOCOL_VERSION, META_SERVER_INFO, METHOD_NOT_FOUND, MISSING_REQUIRED_CLIENT_CAPABILITY, MODERN_VERSIONS, PARSE_ERROR, ProtocolError, UNSUPPORTED_PROTOCOL_VERSION, } from "./protocol.js";
8
+ import { callDigest, RequestStates } from "./request-state.js";
9
+ import { rawInputOf, toolAccess, toolDefinition } from "./tool-definitions.js";
10
+ import { commandToolResult, errorToolResult } from "./tool-results.js";
11
+ /** How long a client may keep the tool list, which changes only when the server starts again. */
12
+ const CACHE_TTL_MS = 300_000;
13
+ /** Output a program prints through a command, such as docker's logs, kept from its end. */
14
+ const OUTPUT_KEPT = 1_000_000;
15
+ const SERVER_CAPABILITIES = { tools: {} };
16
+ /**
17
+ * Read commands become tools, and sensitive read, write and destroy commands when the user
18
+ * allows them. A command that runs a program the user names, or one only for a terminal, never does.
19
+ * The generated docs say the same of each command through this function.
20
+ */
21
+ export function offeredAsTool(spec, settings) {
22
+ if (spec.runsUserCommands || spec.terminalOnly === true)
23
+ return false;
24
+ if (spec.sensitive === true && !settings.allowWrite)
25
+ return false;
26
+ if (spec.safety === "write")
27
+ return settings.allowWrite;
28
+ if (spec.safety === "destroy")
29
+ return settings.allowDestroy;
30
+ return true;
31
+ }
32
+ const keyOf = (id) => `${typeof id}:${id}`;
33
+ /**
34
+ * The protocol, apart from how messages travel. It serves the 2026-07-28 version, whose
35
+ * requests carry their own version, and the earlier ones, which begin with `initialize`.
36
+ */
37
+ export class McpServer {
38
+ options;
39
+ tools = new Map();
40
+ running = new Map();
41
+ pending = new Set();
42
+ /** Questions this server asked a client of an earlier version, by request id. */
43
+ questions = new Map();
44
+ states;
45
+ nextQuestion = 1;
46
+ legacy = null;
47
+ constructor(options) {
48
+ this.options = options;
49
+ this.states = new RequestStates(options.secret, options.now);
50
+ const entries = options.registry
51
+ .list()
52
+ .flatMap((module) => [...module.commands.values()])
53
+ .filter((entry) => offeredAsTool(entry.command.spec, options))
54
+ .sort((a, b) => byCodePoint(a.command.spec.id, b.command.spec.id));
55
+ for (const entry of entries) {
56
+ const { spec } = entry.command;
57
+ try {
58
+ this.tools.set(spec.id, { entry, definition: toolDefinition(spec, options.translator, options.allowWrite) });
59
+ }
60
+ catch (error) {
61
+ options.log(message("core.mcp.tool-left-out", { tool: spec.id, detail: String(error) }));
62
+ }
63
+ }
64
+ }
65
+ /** One line from the client. Requests run side by side, and each answer is sent when it is ready. */
66
+ receive(line) {
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(line);
70
+ }
71
+ catch {
72
+ this.fail(null, new ProtocolError(PARSE_ERROR, message("core.mcp.error.parse")));
73
+ return;
74
+ }
75
+ const invalid = new ProtocolError(INVALID_REQUEST, message("core.mcp.error.invalid-request"));
76
+ if (!isObject(parsed) || parsed["jsonrpc"] !== "2.0") {
77
+ this.fail(null, invalid);
78
+ return;
79
+ }
80
+ const { id, method, params } = parsed;
81
+ if (typeof method !== "string") {
82
+ // A response, which can only answer a question this server asked.
83
+ const answered = typeof id === "string" ? this.questions.get(id) : undefined;
84
+ if (answered !== undefined)
85
+ answered(answerOf(parsed["result"]));
86
+ else if (!("result" in parsed) && !("error" in parsed))
87
+ this.fail(null, invalid);
88
+ return;
89
+ }
90
+ if (id === undefined) {
91
+ this.notification(method, params);
92
+ return;
93
+ }
94
+ if (typeof id !== "string" && typeof id !== "number") {
95
+ this.fail(null, invalid);
96
+ return;
97
+ }
98
+ const work = this.request(id, method, params).finally(() => this.pending.delete(work));
99
+ this.pending.add(work);
100
+ }
101
+ /** Stops every running command and waits for it, as when the client closes the connection. */
102
+ async close() {
103
+ for (const controller of this.running.values())
104
+ controller.abort();
105
+ await Promise.allSettled([...this.pending]);
106
+ }
107
+ async request(id, method, params) {
108
+ const key = keyOf(id);
109
+ const controller = new AbortController();
110
+ this.running.set(key, controller);
111
+ try {
112
+ const result = await this.dispatch(method, params, controller.signal);
113
+ if (!controller.signal.aborted)
114
+ this.options.send({ jsonrpc: "2.0", id, result });
115
+ }
116
+ catch (error) {
117
+ // A cancelled request gets no answer at all.
118
+ if (controller.signal.aborted)
119
+ return;
120
+ if (error instanceof ProtocolError) {
121
+ this.fail(id, error);
122
+ }
123
+ else {
124
+ const detail = error instanceof Error ? error.message : String(error);
125
+ this.fail(id, new ProtocolError(INTERNAL_ERROR, message("core.error.unexpected", { detail })));
126
+ }
127
+ }
128
+ finally {
129
+ if (this.running.get(key) === controller)
130
+ this.running.delete(key);
131
+ }
132
+ }
133
+ async dispatch(method, params, signal) {
134
+ const given = params ?? {};
135
+ if (!isObject(given))
136
+ throw new ProtocolError(INVALID_PARAMS, message("core.mcp.error.params-not-object"));
137
+ switch (method) {
138
+ case "initialize":
139
+ return this.initialize(given);
140
+ case "ping":
141
+ return {};
142
+ case "server/discover":
143
+ return this.discover(given);
144
+ case "tools/list":
145
+ return this.listTools(this.era(given), given);
146
+ case "tools/call":
147
+ return this.callTool(this.era(given), given, signal);
148
+ default:
149
+ throw new ProtocolError(METHOD_NOT_FOUND, message("core.mcp.error.unknown-method", { method }));
150
+ }
151
+ }
152
+ notification(method, params) {
153
+ if (method !== "notifications/cancelled" || !isObject(params))
154
+ return;
155
+ const { requestId } = params;
156
+ if (typeof requestId === "string" || typeof requestId === "number")
157
+ this.running.get(keyOf(requestId))?.abort();
158
+ }
159
+ /** A modern request names its version and capabilities in `_meta`; one without belongs to the legacy session. */
160
+ era(params) {
161
+ const meta = metaOf(params);
162
+ const version = meta[META_PROTOCOL_VERSION];
163
+ if (version === undefined) {
164
+ if (this.legacy !== null)
165
+ return this.legacy;
166
+ throw new ProtocolError(INVALID_PARAMS, message("core.mcp.error.no-version"));
167
+ }
168
+ if (typeof version !== "string" || !MODERN_VERSIONS.includes(version)) {
169
+ throw new ProtocolError(UNSUPPORTED_PROTOCOL_VERSION, message("core.mcp.error.unsupported-version", { version: String(version) }), { supported: [...MODERN_VERSIONS, ...LEGACY_VERSIONS], requested: version });
170
+ }
171
+ const capabilities = meta[META_CLIENT_CAPABILITIES];
172
+ if (!isObject(capabilities))
173
+ throw new ProtocolError(INVALID_PARAMS, message("core.mcp.error.no-capabilities"));
174
+ return { modern: true, version, capabilities };
175
+ }
176
+ /** The handshake of the versions before 2026-07-28. The session it opens lasts as long as the process. */
177
+ initialize(params) {
178
+ const requested = params["protocolVersion"];
179
+ const capabilities = params["capabilities"];
180
+ const version = typeof requested === "string" && LEGACY_VERSIONS.includes(requested) ? requested : LATEST_LEGACY_VERSION;
181
+ this.legacy = { modern: false, version, capabilities: isObject(capabilities) ? capabilities : {} };
182
+ return {
183
+ protocolVersion: version,
184
+ capabilities: SERVER_CAPABILITIES,
185
+ serverInfo: this.serverInfo(),
186
+ instructions: this.instructions(),
187
+ };
188
+ }
189
+ /** Answers with or without `_meta`, since a client may probe with it; a version it names must still be supported. */
190
+ discover(params) {
191
+ if (metaOf(params)[META_PROTOCOL_VERSION] !== undefined)
192
+ this.era(params);
193
+ return this.complete({ modern: true, version: MODERN_VERSIONS[0] ?? "", capabilities: {} }, {
194
+ supportedVersions: [...MODERN_VERSIONS],
195
+ capabilities: SERVER_CAPABILITIES,
196
+ instructions: this.instructions(),
197
+ ttlMs: CACHE_TTL_MS,
198
+ cacheScope: "private",
199
+ });
200
+ }
201
+ /** A client that cannot ask its user in form mode is never offered a tool whose work cannot be undone. */
202
+ listTools(era, params) {
203
+ if (params["cursor"] !== undefined)
204
+ throw new ProtocolError(INVALID_PARAMS, message("core.mcp.error.unknown-cursor"));
205
+ const canAsk = supportsFormElicitation(era.capabilities);
206
+ const tools = [...this.tools.values()]
207
+ .filter((tool) => canAsk || tool.entry.command.spec.safety !== "destroy")
208
+ .map((tool) => tool.definition);
209
+ return this.complete(era, era.modern ? { tools, ttlMs: CACHE_TTL_MS, cacheScope: "private" } : { tools });
210
+ }
211
+ /**
212
+ * Runs the command as the CLI would, inside the roots. A failure the model can correct,
213
+ * such as a bad value or a refused path, is a result with `isError`; an unknown tool is a
214
+ * protocol error. A question for the user ends the round with an input-required result
215
+ * in the modern version, and becomes a request to the client in the earlier ones.
216
+ */
217
+ async callTool(era, params, signal) {
218
+ const name = params["name"];
219
+ if (typeof name !== "string")
220
+ throw new ProtocolError(INVALID_PARAMS, message("core.mcp.error.no-tool-name"));
221
+ const tool = this.tools.get(name);
222
+ if (tool === undefined)
223
+ throw new ProtocolError(INVALID_PARAMS, message("core.mcp.error.unknown-tool", { name }));
224
+ const { command } = tool.entry;
225
+ const { spec } = command;
226
+ const canAsk = supportsFormElicitation(era.capabilities);
227
+ if (spec.safety === "destroy" && !canAsk) {
228
+ throw new ProtocolError(MISSING_REQUIRED_CLIENT_CAPABILITY, message("core.mcp.error.needs-elicitation", { name }), { requiredCapabilities: { elicitation: { form: {} } } });
229
+ }
230
+ const digest = callDigest(name, params["arguments"]);
231
+ const elicitation = this.options.allowDestroy && canAsk ? this.elicitation(era, params, name, digest, signal) : null;
232
+ const { scope, translator } = this.options;
233
+ let output = "";
234
+ let result;
235
+ try {
236
+ const raw = rawInputOf(spec.input, params["arguments"], toolAccess(spec, this.options.allowWrite));
237
+ await scope.refuseOutside(pathValues(spec.input, raw), spec.safety !== "read");
238
+ const input = spec.input.parse(raw);
239
+ const outcome = await command.execute(input, {
240
+ cwd: scope.start,
241
+ signal,
242
+ confirmation: new McpConfirmation(spec.safety, elicitation, translator),
243
+ passthrough: {
244
+ write: (text) => {
245
+ output = (output + text).slice(-OUTPUT_KEPT);
246
+ },
247
+ },
248
+ });
249
+ result = commandToolResult(spec.id, outcome, output, translator);
250
+ }
251
+ catch (error) {
252
+ if (error instanceof InputRequired) {
253
+ return {
254
+ resultType: "input_required",
255
+ inputRequests: error.inputRequests,
256
+ requestState: error.requestState,
257
+ _meta: { [META_SERVER_INFO]: this.serverInfo() },
258
+ };
259
+ }
260
+ if (!(error instanceof KiriyaError))
261
+ throw error;
262
+ result = errorToolResult(error, translator);
263
+ }
264
+ return this.complete(era, { ...result });
265
+ }
266
+ /** How this call asks its user: across rounds in the modern version, by a request of the server's own before it. */
267
+ elicitation(era, params, name, digest, signal) {
268
+ if (!era.modern)
269
+ return { ask: (question) => this.askClient(question, era, signal) };
270
+ const answers = this.answersOf(params, name, digest);
271
+ return new RoundTripElicitation(answers, (key, question) => {
272
+ const request = { method: "elicitation/create", params: { mode: "form", ...question } };
273
+ return new InputRequired({ [key]: request }, this.states.issue({ tool: name, digest, answers, key }));
274
+ });
275
+ }
276
+ /**
277
+ * The answers a retried call brings: those of earlier rounds, carried in the signed state,
278
+ * and the one to the question the last round asked. A state this server did not issue for
279
+ * this very call is refused; answers without a state are ignored, so no question is skipped.
280
+ */
281
+ answersOf(params, name, digest) {
282
+ const given = params["requestState"];
283
+ if (given === undefined)
284
+ return {};
285
+ const state = this.states.read(given);
286
+ const key = state?.["key"];
287
+ if (state === null || state["tool"] !== name || state["digest"] !== digest || typeof key !== "string") {
288
+ throw new ProtocolError(INVALID_PARAMS, message("core.mcp.error.bad-request-state"));
289
+ }
290
+ const answers = {};
291
+ const earlier = state["answers"];
292
+ if (isObject(earlier)) {
293
+ for (const [question, answer] of Object.entries(earlier))
294
+ answers[question] = answerOf(answer);
295
+ }
296
+ const responses = params["inputResponses"];
297
+ const response = isObject(responses) ? responses[key] : undefined;
298
+ if (response !== undefined)
299
+ answers[key] = answerOf(response);
300
+ return answers;
301
+ }
302
+ /** Before 2026-07-28 the server asks with a request of its own and waits for the client's answer. */
303
+ askClient(question, era, signal) {
304
+ const id = `kiriya-${this.nextQuestion}`;
305
+ this.nextQuestion += 1;
306
+ return new Promise((resolve, reject) => {
307
+ const stop = () => {
308
+ this.questions.delete(id);
309
+ reject(new InterruptedError("core.error.interrupted"));
310
+ };
311
+ if (signal.aborted) {
312
+ stop();
313
+ return;
314
+ }
315
+ signal.addEventListener("abort", stop, { once: true });
316
+ this.questions.set(id, (answer) => {
317
+ signal.removeEventListener("abort", stop);
318
+ this.questions.delete(id);
319
+ resolve(answer);
320
+ });
321
+ // 2025-06-18, the first version with elicitation, has no mode field; 2025-11-25 names it.
322
+ const params = era.version === "2025-06-18" ? { ...question } : { mode: "form", ...question };
323
+ this.options.send({ jsonrpc: "2.0", id, method: "elicitation/create", params });
324
+ });
325
+ }
326
+ /** Results of the modern version say they are complete and name the server. */
327
+ complete(era, body) {
328
+ return era.modern ? { resultType: "complete", ...body, _meta: { [META_SERVER_INFO]: this.serverInfo() } } : body;
329
+ }
330
+ serverInfo() {
331
+ return { name: "kiriya", version: this.options.version };
332
+ }
333
+ instructions() {
334
+ const { scope, translator, allowWrite, allowDestroy } = this.options;
335
+ const tools = message(allowDestroy
336
+ ? "core.mcp.instructions.destroy"
337
+ : allowWrite
338
+ ? "core.mcp.instructions.write"
339
+ : "core.mcp.instructions.read-only");
340
+ return translator.text(message("core.mcp.instructions", { tools, start: scope.start, roots: scope.roots.join(", ") }));
341
+ }
342
+ fail(id, error) {
343
+ const detail = { code: error.code, message: this.options.translator.text(error.detail) };
344
+ this.options.send({
345
+ jsonrpc: "2.0",
346
+ id,
347
+ error: error.data === undefined ? detail : { ...detail, data: error.data },
348
+ });
349
+ }
350
+ }
351
+ function metaOf(params) {
352
+ const meta = params["_meta"];
353
+ return isObject(meta) ? meta : {};
354
+ }
355
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1,32 @@
1
+ /** Versions whose every request carries its version and client capabilities in `_meta`. */
2
+ export const MODERN_VERSIONS = ["2026-07-28"];
3
+ /** Versions that open with the `initialize` handshake, newest first. */
4
+ export const LEGACY_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
5
+ export const LATEST_LEGACY_VERSION = "2025-11-25";
6
+ export const META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
7
+ export const META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
8
+ export const META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
9
+ export const PARSE_ERROR = -32700;
10
+ export const INVALID_REQUEST = -32600;
11
+ export const METHOD_NOT_FOUND = -32601;
12
+ export const INVALID_PARAMS = -32602;
13
+ export const INTERNAL_ERROR = -32603;
14
+ export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
15
+ export const UNSUPPORTED_PROTOCOL_VERSION = -32022;
16
+ export function isObject(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+ /** A request the server answers with a JSON-RPC error instead of a result. */
20
+ export class ProtocolError extends Error {
21
+ code;
22
+ detail;
23
+ data;
24
+ constructor(code, detail, data) {
25
+ super(detail.key);
26
+ this.code = code;
27
+ this.detail = detail;
28
+ this.data = data;
29
+ this.name = "ProtocolError";
30
+ }
31
+ }
32
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1,66 @@
1
+ import { createHash, createHmac, timingSafeEqual } from "node:crypto";
2
+ import { isObject } from "./protocol.js";
3
+ /** How long a question asked over MCP stays open for its answer. */
4
+ export const REQUEST_STATE_TTL_MS = 15 * 60_000;
5
+ /** A JSON value as text with its keys sorted, so equal arguments always give the same digest. */
6
+ function canonical(value) {
7
+ if (Array.isArray(value))
8
+ return `[${value.map(canonical).join(",")}]`;
9
+ if (isObject(value)) {
10
+ const members = Object.keys(value)
11
+ .sort()
12
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`);
13
+ return `{${members.join(",")}}`;
14
+ }
15
+ return JSON.stringify(value) ?? "null";
16
+ }
17
+ /** Names one call: the tool and its arguments. */
18
+ export function callDigest(tool, args) {
19
+ return createHash("sha256")
20
+ .update(`${tool}\n${canonical(args ?? {})}`)
21
+ .digest("base64url");
22
+ }
23
+ /**
24
+ * The opaque `requestState` of a multi round-trip request. The client carries it between
25
+ * rounds, so it is signed with a secret of this process and dated: a state the process
26
+ * did not issue, one that was altered, and one past its time are all rejected.
27
+ */
28
+ export class RequestStates {
29
+ secret;
30
+ now;
31
+ constructor(secret, now) {
32
+ this.secret = secret;
33
+ this.now = now;
34
+ }
35
+ issue(payload) {
36
+ const body = Buffer.from(JSON.stringify({ ...payload, expires: this.now() + REQUEST_STATE_TTL_MS })).toString("base64url");
37
+ return `${body}.${this.sign(body)}`;
38
+ }
39
+ /** The payload, or null for a state that is forged, altered or expired. */
40
+ read(state) {
41
+ if (typeof state !== "string")
42
+ return null;
43
+ const [body, signature, ...rest] = state.split(".");
44
+ if (body === undefined || signature === undefined || rest.length > 0)
45
+ return null;
46
+ const expected = Buffer.from(this.sign(body));
47
+ const given = Buffer.from(signature);
48
+ if (expected.length !== given.length || !timingSafeEqual(expected, given))
49
+ return null;
50
+ let payload;
51
+ try {
52
+ payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ if (!isObject(payload))
58
+ return null;
59
+ const expires = payload["expires"];
60
+ return typeof expires === "number" && expires >= this.now() ? payload : null;
61
+ }
62
+ sign(body) {
63
+ return createHmac("sha256", this.secret).update(body).digest("base64url");
64
+ }
65
+ }
66
+ //# sourceMappingURL=request-state.js.map