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,65 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ /** Runs a program without a shell, so no argument is ever re-parsed. */
5
+ export function runProgram(command, args, options = {}) {
6
+ return new Promise((resolve, reject) => {
7
+ const started = performance.now();
8
+ const output = options.ignoreOutput === true ? "ignore" : "pipe";
9
+ const child = spawn(command, [...args], {
10
+ env: options.env === undefined ? process.env : { ...process.env, ...options.env },
11
+ windowsHide: true,
12
+ stdio: ["pipe", output, output],
13
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
14
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
15
+ });
16
+ const out = [];
17
+ const err = [];
18
+ const timeoutMs = options.timeoutMs ?? 60_000;
19
+ const timer = timeoutMs > 0 ? setTimeout(() => child.kill(), timeoutMs) : undefined;
20
+ const onOutput = options.onOutput;
21
+ if (onOutput === undefined) {
22
+ child.stdout?.on("data", (chunk) => out.push(chunk));
23
+ child.stderr?.on("data", (chunk) => err.push(chunk));
24
+ }
25
+ else {
26
+ // Decoding per stream keeps a character split across two chunks whole.
27
+ child.stdout?.setEncoding("utf8");
28
+ child.stderr?.setEncoding("utf8");
29
+ child.stdout?.on("data", (text) => onOutput(text, "stdout"));
30
+ child.stderr?.on("data", (text) => onOutput(text, "stderr"));
31
+ }
32
+ child.on("error", (error) => {
33
+ clearTimeout(timer);
34
+ reject(error);
35
+ });
36
+ const finish = (code) => {
37
+ clearTimeout(timer);
38
+ resolve({
39
+ code: code ?? 1,
40
+ stdout: Buffer.concat(out).toString("utf8"),
41
+ stderr: Buffer.concat(err).toString("utf8"),
42
+ ms: performance.now() - started,
43
+ });
44
+ };
45
+ if (options.ignoreOutput === true)
46
+ child.on("exit", finish);
47
+ else
48
+ child.on("close", finish);
49
+ // A program may exit without reading its input, and writing to its closed pipe raises
50
+ // EPIPE. That is not a failure of the run: the exit code and output still tell the result.
51
+ child.stdin?.on("error", () => undefined);
52
+ child.stdin?.end(options.input ?? "");
53
+ });
54
+ }
55
+ export function windowsPowerShell() {
56
+ const inbox = join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
57
+ return existsSync(inbox) ? inbox : "powershell.exe";
58
+ }
59
+ /** A Windows PowerShell script through -EncodedCommand. Data travels in environment variables or stdin, never in the script. */
60
+ export function runPowerShell(script, options = {}) {
61
+ // Without this, progress records such as "Preparing modules for first use" arrive on stderr as CLIXML.
62
+ const encoded = Buffer.from(`$ProgressPreference = 'SilentlyContinue'\n${script}`, "utf16le").toString("base64");
63
+ return runProgram(windowsPowerShell(), ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded], options);
64
+ }
65
+ //# sourceMappingURL=run-program.js.map
@@ -0,0 +1,22 @@
1
+ import { InterruptedError } from "../../domain/errors.js";
2
+ export class SystemClockAdapter {
3
+ now() {
4
+ return Date.now();
5
+ }
6
+ sleep(ms, signal) {
7
+ if (signal.aborted)
8
+ return Promise.reject(new InterruptedError("core.error.interrupted"));
9
+ return new Promise((resolve, reject) => {
10
+ const onAbort = () => {
11
+ clearTimeout(timer);
12
+ reject(new InterruptedError("core.error.interrupted"));
13
+ };
14
+ const timer = setTimeout(() => {
15
+ signal.removeEventListener("abort", onAbort);
16
+ resolve();
17
+ }, ms);
18
+ signal.addEventListener("abort", onAbort, { once: true });
19
+ });
20
+ }
21
+ }
22
+ //# sourceMappingURL=system-clock.adapter.js.map
@@ -0,0 +1,45 @@
1
+ import { spawn } from "node:child_process";
2
+ import { CapabilityUnavailableError, InterruptedError } from "../../domain/errors.js";
3
+ import { runPowerShell, runProgram } from "./run-program.js";
4
+ function cannotStart(program, error) {
5
+ const detail = error instanceof Error ? error.message : String(error);
6
+ return new CapabilityUnavailableError("core.process.cannot-start", { program, detail }, { cause: error });
7
+ }
8
+ /** The first line of a program's error output, for a message. */
9
+ export function firstLine(text) {
10
+ return text.trim().split(/\r?\n/)[0] ?? "";
11
+ }
12
+ /** runProgram with kiriya's errors: InterruptedError when the signal stopped it, CapabilityUnavailableError when it could not start. */
13
+ export async function runTyped(command, args, options = {}) {
14
+ try {
15
+ return await runProgram(command, args, options);
16
+ }
17
+ catch (error) {
18
+ if (options.signal?.aborted === true)
19
+ throw new InterruptedError("core.error.interrupted", {}, { cause: error });
20
+ throw cannotStart(command, error);
21
+ }
22
+ }
23
+ /** runPowerShell with the same errors as runTyped. */
24
+ export async function runPowerShellTyped(script, options = {}) {
25
+ try {
26
+ return await runPowerShell(script, options);
27
+ }
28
+ catch (error) {
29
+ if (options.signal?.aborted === true)
30
+ throw new InterruptedError("core.error.interrupted", {}, { cause: error });
31
+ throw cannotStart("powershell.exe", error);
32
+ }
33
+ }
34
+ /** Starts a program that outlives kiriya, such as the application a file opens in, and returns once it has started. */
35
+ export function launchDetached(command, args) {
36
+ return new Promise((resolve, reject) => {
37
+ const child = spawn(command, [...args], { detached: true, stdio: "ignore" });
38
+ child.once("error", (error) => reject(cannotStart(command, error)));
39
+ child.once("spawn", () => {
40
+ child.unref();
41
+ resolve();
42
+ });
43
+ });
44
+ }
45
+ //# sourceMappingURL=typed-programs.js.map
@@ -0,0 +1,115 @@
1
+ import { constants } from "node:fs";
2
+ import { access, cp, lstat, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { message } from "../../../domain/message.js";
5
+ const NEWLINE = String.fromCharCode(10);
6
+ async function exists(path) {
7
+ try {
8
+ await access(path, constants.F_OK);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ function localTime(date) {
16
+ const pad = (value) => String(value).padStart(2, "0");
17
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
18
+ }
19
+ /**
20
+ * The freedesktop.org trash specification implemented directly, so files on other
21
+ * mounts work where `gio trash` refuses them. Verified in the phase 0 spike.
22
+ */
23
+ export class FreedesktopTrashAdapter {
24
+ environment;
25
+ location = "core.trash.location.freedesktop";
26
+ constructor(environment) {
27
+ this.environment = environment;
28
+ }
29
+ async send(paths) {
30
+ const outcomes = [];
31
+ for (const path of paths)
32
+ outcomes.push(await this.sendOne(path));
33
+ return outcomes;
34
+ }
35
+ homeTrash() {
36
+ const dataHome = this.environment.variable("XDG_DATA_HOME");
37
+ return join(dataHome !== undefined && isAbsolute(dataHome)
38
+ ? dataHome
39
+ : join(this.environment.homeDirectory, ".local", "share"), "Trash");
40
+ }
41
+ async prepare(root) {
42
+ await mkdir(join(root, "files"), { recursive: true, mode: 0o700 });
43
+ await mkdir(join(root, "info"), { recursive: true, mode: 0o700 });
44
+ }
45
+ async directoryFor(path) {
46
+ const home = this.homeTrash();
47
+ await this.prepare(home);
48
+ const device = (await lstat(path)).dev;
49
+ if (device === (await stat(home)).dev)
50
+ return { root: home, topdir: null, copy: false };
51
+ let topdir = resolve(dirname(path));
52
+ while (dirname(topdir) !== topdir && (await stat(dirname(topdir))).dev === device)
53
+ topdir = dirname(topdir);
54
+ const uid = process.getuid?.() ?? 0;
55
+ try {
56
+ const shared = await lstat(join(topdir, ".Trash"));
57
+ if (shared.isDirectory() && !shared.isSymbolicLink() && (shared.mode & 0o1000) !== 0) {
58
+ const root = join(topdir, ".Trash", String(uid));
59
+ await this.prepare(root);
60
+ return { root, topdir, copy: false };
61
+ }
62
+ }
63
+ catch {
64
+ // no shared trash on this mount
65
+ }
66
+ try {
67
+ const root = join(topdir, `.Trash-${uid}`);
68
+ await this.prepare(root);
69
+ return { root, topdir, copy: false };
70
+ }
71
+ catch {
72
+ return { root: home, topdir: null, copy: true };
73
+ }
74
+ }
75
+ async freeName(root, name) {
76
+ const extension = extname(name);
77
+ const stem = name.slice(0, name.length - extension.length);
78
+ for (let attempt = 1;; attempt += 1) {
79
+ const candidate = attempt === 1 ? name : `${stem}.${attempt}${extension}`;
80
+ if (!(await exists(join(root, "files", candidate))) &&
81
+ !(await exists(join(root, "info", `${candidate}.trashinfo`)))) {
82
+ return candidate;
83
+ }
84
+ }
85
+ }
86
+ async sendOne(path) {
87
+ let info;
88
+ try {
89
+ const target = await this.directoryFor(path);
90
+ const name = await this.freeName(target.root, basename(path));
91
+ info = join(target.root, "info", `${name}.trashinfo`);
92
+ const recorded = target.topdir === null ? resolve(path) : relative(target.topdir, resolve(path));
93
+ const encoded = recorded.split(sep).map(encodeURIComponent).join("/");
94
+ await writeFile(info, ["[Trash Info]", `Path=${encoded}`, `DeletionDate=${localTime(new Date())}`, ""].join(NEWLINE), {
95
+ flag: "wx",
96
+ });
97
+ const destination = join(target.root, "files", name);
98
+ if (target.copy) {
99
+ await cp(path, destination, { recursive: true, preserveTimestamps: true, verbatimSymlinks: true });
100
+ await rm(path, { recursive: true, force: true });
101
+ }
102
+ else {
103
+ await rename(path, destination);
104
+ }
105
+ return { path, ok: true };
106
+ }
107
+ catch (error) {
108
+ if (info !== undefined)
109
+ await rm(info, { force: true });
110
+ const code = error.code ?? error.message;
111
+ return { path, ok: false, reason: message("core.trash.failed", { detail: code }) };
112
+ }
113
+ }
114
+ }
115
+ //# sourceMappingURL=freedesktop-trash.adapter.js.map
@@ -0,0 +1,70 @@
1
+ import { CapabilityUnavailableError, OperationFailedError } from "../../../domain/errors.js";
2
+ import { firstLine, runTyped } from "../../node/typed-programs.js";
3
+ /** What xclip, xsel and wl-paste say about a clipboard with no text on it. */
4
+ const EMPTY = /not available|nothing is copied|no selection/i;
5
+ /**
6
+ * wl-clipboard in a Wayland session, then xclip or xsel under X11. A session with no
7
+ * display has no clipboard at all, which is reported rather than faked.
8
+ */
9
+ export class LinuxClipboardAdapter {
10
+ environment;
11
+ processRunner;
12
+ constructor(environment, processRunner) {
13
+ this.environment = environment;
14
+ this.processRunner = processRunner;
15
+ }
16
+ async backend() {
17
+ return (await this.program()).name;
18
+ }
19
+ async write(text, signal) {
20
+ const program = await this.program();
21
+ const [command, args] = program.copy;
22
+ // These programs stay in the background to serve the clipboard, holding their output open, so only their exit counts.
23
+ const result = await runTyped(command, args, { input: text, signal, ignoreOutput: true, timeoutMs: 30_000 });
24
+ if (result.code !== 0) {
25
+ throw new OperationFailedError("core.clipboard.failed", { program: program.name, code: result.code, detail: "" });
26
+ }
27
+ }
28
+ async read(signal) {
29
+ const program = await this.program();
30
+ const [command, args] = program.paste;
31
+ const result = await runTyped(command, args, { signal, timeoutMs: 30_000 });
32
+ if (result.code === 0)
33
+ return result.stdout;
34
+ if (EMPTY.test(result.stderr))
35
+ return "";
36
+ const detail = firstLine(result.stderr);
37
+ throw new OperationFailedError("core.clipboard.failed", { program: program.name, code: result.code, detail });
38
+ }
39
+ async program() {
40
+ const wayland = (this.environment.variable("WAYLAND_DISPLAY") ?? "") !== "";
41
+ const x11 = (this.environment.variable("DISPLAY") ?? "") !== "";
42
+ if (wayland) {
43
+ const [copy, paste] = await Promise.all([
44
+ this.processRunner.find("wl-copy"),
45
+ this.processRunner.find("wl-paste"),
46
+ ]);
47
+ if (copy !== null && paste !== null) {
48
+ return { name: "wl-clipboard", copy: [copy, []], paste: [paste, ["--no-newline"]] };
49
+ }
50
+ }
51
+ if (x11) {
52
+ const xclip = await this.processRunner.find("xclip");
53
+ if (xclip !== null) {
54
+ return {
55
+ name: "xclip",
56
+ copy: [xclip, ["-selection", "clipboard", "-in"]],
57
+ paste: [xclip, ["-selection", "clipboard", "-out"]],
58
+ };
59
+ }
60
+ const xsel = await this.processRunner.find("xsel");
61
+ if (xsel !== null) {
62
+ return { name: "xsel", copy: [xsel, ["--clipboard", "--input"]], paste: [xsel, ["--clipboard", "--output"]] };
63
+ }
64
+ }
65
+ if (wayland || x11)
66
+ throw new CapabilityUnavailableError("core.clipboard.no-program");
67
+ throw new CapabilityUnavailableError("core.clipboard.no-display");
68
+ }
69
+ }
70
+ //# sourceMappingURL=linux-clipboard.adapter.js.map
@@ -0,0 +1,21 @@
1
+ import { CapabilityUnavailableError } from "../../../domain/errors.js";
2
+ import { launchDetached } from "../../node/typed-programs.js";
3
+ /** xdg-open, started without waiting, because some desktops keep it running until the application closes. */
4
+ export class LinuxOpenerAdapter {
5
+ environment;
6
+ processRunner;
7
+ constructor(environment, processRunner) {
8
+ this.environment = environment;
9
+ this.processRunner = processRunner;
10
+ }
11
+ async open(target) {
12
+ const hasDisplay = [this.environment.variable("WAYLAND_DISPLAY"), this.environment.variable("DISPLAY")].some((value) => (value ?? "") !== "");
13
+ if (!hasDisplay)
14
+ throw new CapabilityUnavailableError("core.open.no-display", { target });
15
+ const xdgOpen = await this.processRunner.find("xdg-open");
16
+ if (xdgOpen === null)
17
+ throw new CapabilityUnavailableError("core.open.no-xdg-open");
18
+ await launchDetached(xdgOpen, [target]);
19
+ }
20
+ }
21
+ //# sourceMappingURL=linux-opener.adapter.js.map
@@ -0,0 +1,97 @@
1
+ import { readdir, readFile, readlink } from "node:fs/promises";
2
+ import { CapabilityUnavailableError } from "../../../domain/errors.js";
3
+ import { canListen } from "../../node/can-listen.js";
4
+ function compressIpv6(groups) {
5
+ let bestStart = -1;
6
+ let bestLength = 0;
7
+ for (let index = 0; index < groups.length;) {
8
+ if (groups[index] !== "0") {
9
+ index += 1;
10
+ continue;
11
+ }
12
+ let end = index;
13
+ while (end < groups.length && groups[end] === "0")
14
+ end += 1;
15
+ if (end - index >= 2 && end - index > bestLength) {
16
+ bestStart = index;
17
+ bestLength = end - index;
18
+ }
19
+ index = end;
20
+ }
21
+ if (bestStart < 0)
22
+ return groups.join(":");
23
+ return `${groups.slice(0, bestStart).join(":")}::${groups.slice(bestStart + bestLength).join(":")}`;
24
+ }
25
+ /** A hexadecimal address from /proc/net/tcp: IPv4 as one little-endian 32-bit word, IPv6 as four. */
26
+ export function decodeProcAddress(hex) {
27
+ const pairs = (word) => word.match(/../g) ?? [];
28
+ if (hex.length === 8) {
29
+ return pairs(hex)
30
+ .reverse()
31
+ .map((byte) => parseInt(byte, 16))
32
+ .join(".");
33
+ }
34
+ if (hex.length !== 32)
35
+ return hex;
36
+ const bytes = (hex.match(/.{8}/g) ?? []).flatMap((word) => pairs(word).reverse());
37
+ const groups = [];
38
+ for (let index = 0; index < bytes.length; index += 2) {
39
+ groups.push(parseInt(`${bytes[index] ?? "00"}${bytes[index + 1] ?? "00"}`, 16).toString(16));
40
+ }
41
+ return compressIpv6(groups);
42
+ }
43
+ /** Listening sockets from /proc/net/tcp or tcp6: state 0A is LISTEN, and the inode names the socket. */
44
+ export function parseProcNetTcp(text) {
45
+ return text
46
+ .split("\n")
47
+ .slice(1)
48
+ .flatMap((line) => {
49
+ const parts = line.trim().split(/\s+/);
50
+ if (parts.length < 10 || parts[3] !== "0A")
51
+ return [];
52
+ const [address = "", port = ""] = (parts[1] ?? "").split(":");
53
+ return [{ address: decodeProcAddress(address), port: parseInt(port, 16), inode: parts[9] ?? "" }];
54
+ });
55
+ }
56
+ /** Socket inodes and the processes holding them, from every /proc/<pid>/fd this user may read. */
57
+ async function socketOwners() {
58
+ const owners = new Map();
59
+ const pids = (await readdir("/proc")).filter((entry) => /^\d+$/.test(entry));
60
+ for (const pid of pids) {
61
+ const descriptors = await readdir(`/proc/${pid}/fd`).catch(() => []);
62
+ const links = await Promise.all(descriptors.map((descriptor) => readlink(`/proc/${pid}/fd/${descriptor}`).catch(() => "")));
63
+ for (const link of links) {
64
+ if (link.startsWith("socket:["))
65
+ owners.set(link.slice(8, -1), Number(pid));
66
+ }
67
+ }
68
+ return owners;
69
+ }
70
+ /**
71
+ * /proc/net/tcp and tcp6 read with node:fs. Another user's socket has no visible owner
72
+ * without elevation, because /proc/<pid>/fd needs ptrace access; kiriya never elevates.
73
+ */
74
+ export class LinuxPortTableAdapter {
75
+ async listeners() {
76
+ const reads = await Promise.allSettled(["/proc/net/tcp", "/proc/net/tcp6"].map((file) => readFile(file, "utf8")));
77
+ const texts = reads.flatMap((read) => (read.status === "fulfilled" ? [read.value] : []));
78
+ const firstFailure = reads.find((read) => read.status === "rejected");
79
+ if (texts.length === 0 && firstFailure !== undefined) {
80
+ const detail = firstFailure.reason instanceof Error ? firstFailure.reason.message : String(firstFailure.reason);
81
+ throw new CapabilityUnavailableError("core.ports.unreadable", { source: "/proc/net/tcp", detail });
82
+ }
83
+ const sockets = texts.flatMap(parseProcNetTcp);
84
+ if (sockets.length === 0)
85
+ return [];
86
+ const owners = await socketOwners();
87
+ return sockets.map((socket) => ({
88
+ address: socket.address,
89
+ port: socket.port,
90
+ pid: owners.get(socket.inode) ?? null,
91
+ }));
92
+ }
93
+ canListen(port) {
94
+ return canListen(port);
95
+ }
96
+ }
97
+ //# sourceMappingURL=linux-port-table.adapter.js.map
@@ -0,0 +1,61 @@
1
+ import { readdir, readFile, readlink } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { CapabilityUnavailableError } from "../../../domain/errors.js";
4
+ import { endProcesses } from "../../node/process-signals.js";
5
+ const NUL = String.fromCharCode(0);
6
+ /** The name and parent id in /proc/<pid>/stat, where the name sits in parentheses and may hold spaces and parentheses. */
7
+ export function parseProcStat(stat) {
8
+ const open = stat.indexOf("(");
9
+ const close = stat.lastIndexOf(")");
10
+ if (open < 0 || close < open)
11
+ return null;
12
+ const ppid = Number(stat.slice(close + 2).split(" ")[1]);
13
+ return Number.isInteger(ppid) ? { comm: stat.slice(open + 1, close), ppid } : null;
14
+ }
15
+ /** VmRSS from /proc/<pid>/status in bytes; null for kernel threads, which have none. */
16
+ export function residentMemory(status) {
17
+ const match = /^VmRSS:\s+(\d+)\s+kB$/m.exec(status);
18
+ return match === null ? null : Number(match[1]) * 1024;
19
+ }
20
+ async function readProcess(pid) {
21
+ const folder = `/proc/${pid}`;
22
+ const stat = await readFile(`${folder}/stat`, "utf8").then(parseProcStat, () => null);
23
+ // The process ended while the listing was read.
24
+ if (stat === null)
25
+ return null;
26
+ const parts = (await readFile(`${folder}/cmdline`, "utf8").catch(() => "")).split(NUL).filter((part) => part !== "");
27
+ // Programs may rename their main thread, as Node.js 24 does to MainThread, so the name comes from the executable.
28
+ const executable = await readlink(`${folder}/exe`).catch(() => null);
29
+ const program = parts[0];
30
+ let name = stat.comm;
31
+ if (executable !== null)
32
+ name = path.posix.basename(executable.replace(/ \(deleted\)$/, ""));
33
+ else if (program !== undefined)
34
+ name = path.posix.basename(program);
35
+ const memoryBytes = await readFile(`${folder}/status`, "utf8").then(residentMemory, () => null);
36
+ return { pid, ppid: stat.ppid, name, command: parts.length > 0 ? parts.join(" ") : null, memoryBytes };
37
+ }
38
+ /** /proc read with node:fs: no program starts, and parent ids and command lines cost nothing extra. */
39
+ export class LinuxProcessTableAdapter {
40
+ selfPid = process.pid;
41
+ async list() {
42
+ let entries;
43
+ try {
44
+ entries = await readdir("/proc");
45
+ }
46
+ catch (error) {
47
+ const detail = error instanceof Error ? error.message : String(error);
48
+ throw new CapabilityUnavailableError("core.processes.unreadable", { source: "/proc", detail });
49
+ }
50
+ const pids = entries.filter((entry) => /^\d+$/.test(entry)).map(Number);
51
+ const processes = (await Promise.all(pids.map(readProcess))).filter((entry) => entry !== null);
52
+ return { processes, detailed: true };
53
+ }
54
+ end(pids, force) {
55
+ return endProcesses(pids, force);
56
+ }
57
+ protectedPids() {
58
+ return new Set([0, 1, process.pid, process.ppid]);
59
+ }
60
+ }
61
+ //# sourceMappingURL=linux-process-table.adapter.js.map
@@ -0,0 +1,29 @@
1
+ import { OperationFailedError } from "../../../domain/errors.js";
2
+ import { firstLine, runTyped } from "../../node/typed-programs.js";
3
+ /** pbcopy garbles UTF-8 under the C locale, as the phase 0 spike found, so both programs get a UTF-8 one. */
4
+ const UTF8_LOCALE = { LANG: "en_US.UTF-8", LC_ALL: "en_US.UTF-8", LC_CTYPE: "en_US.UTF-8" };
5
+ function failed(program, result) {
6
+ return new OperationFailedError("core.clipboard.failed", {
7
+ program,
8
+ code: result.code,
9
+ detail: firstLine(result.stderr),
10
+ });
11
+ }
12
+ export class MacosClipboardAdapter {
13
+ backend() {
14
+ return Promise.resolve("pbcopy");
15
+ }
16
+ async write(text, signal) {
17
+ const options = { input: text, env: UTF8_LOCALE, signal, timeoutMs: 30_000 };
18
+ const result = await runTyped("/usr/bin/pbcopy", [], options);
19
+ if (result.code !== 0)
20
+ throw failed("pbcopy", result);
21
+ }
22
+ async read(signal) {
23
+ const result = await runTyped("/usr/bin/pbpaste", [], { env: UTF8_LOCALE, signal, timeoutMs: 30_000 });
24
+ if (result.code !== 0)
25
+ throw failed("pbpaste", result);
26
+ return result.stdout;
27
+ }
28
+ }
29
+ //# sourceMappingURL=macos-clipboard.adapter.js.map
@@ -0,0 +1,13 @@
1
+ import { OperationFailedError } from "../../../domain/errors.js";
2
+ import { firstLine, runTyped } from "../../node/typed-programs.js";
3
+ /** open returns once Launch Services has the target, and fails when no application can open it. */
4
+ export class MacosOpenerAdapter {
5
+ async open(target, signal) {
6
+ const result = await runTyped("/usr/bin/open", [target], { signal, timeoutMs: 60_000 });
7
+ if (result.code !== 0) {
8
+ const detail = firstLine(result.stderr);
9
+ throw new OperationFailedError("core.open.failed", { program: "open", target, code: result.code, detail });
10
+ }
11
+ }
12
+ }
13
+ //# sourceMappingURL=macos-opener.adapter.js.map
@@ -0,0 +1,86 @@
1
+ import { CapabilityUnavailableError } from "../../../domain/errors.js";
2
+ import { canListen } from "../../node/can-listen.js";
3
+ /** `netstat -an -p tcp`: every listening socket, with the port after the address's last dot and * for any address. */
4
+ export function parseMacosNetstat(output) {
5
+ const listeners = [];
6
+ for (const line of output.split("\n")) {
7
+ const parts = line.trim().split(/\s+/);
8
+ const protocol = parts[0] ?? "";
9
+ if (parts.length < 6 || !protocol.startsWith("tcp") || parts[5] !== "LISTEN")
10
+ continue;
11
+ const local = parts[3] ?? "";
12
+ const dot = local.lastIndexOf(".");
13
+ const port = Number(local.slice(dot + 1));
14
+ if (dot < 0 || !Number.isInteger(port))
15
+ continue;
16
+ const host = local.slice(0, dot);
17
+ const address = host === "*" ? (protocol === "tcp4" ? "0.0.0.0" : "::") : host;
18
+ listeners.push({ address, port, pid: null });
19
+ }
20
+ return listeners;
21
+ }
22
+ /** `lsof -nP -iTCP -sTCP:LISTEN -Fpn`: a p line names the process, and each n line after it a socket. */
23
+ export function parseLsofListeners(output) {
24
+ const sockets = [];
25
+ let pid = null;
26
+ for (const line of output.split("\n").map((text) => text.trim())) {
27
+ if (line.startsWith("p")) {
28
+ pid = Number(line.slice(1));
29
+ }
30
+ else if (line.startsWith("n") && pid !== null) {
31
+ const match = /^n(.*):(\d+)$/.exec(line);
32
+ if (match === null)
33
+ continue;
34
+ sockets.push({ pid, address: (match[1] ?? "").replace(/^\[(.*)\]$/, "$1"), port: Number(match[2]) });
35
+ }
36
+ }
37
+ return sockets;
38
+ }
39
+ const ANY_ADDRESS = new Set(["*", "0.0.0.0", "::"]);
40
+ /**
41
+ * netstat sees every listener but no owner; lsof sees owners, but without root only the
42
+ * user's own. Each listener takes its owners from lsof, and a socket only lsof saw is kept.
43
+ */
44
+ export function mergeListeners(all, owned) {
45
+ const used = new Set();
46
+ const merged = [];
47
+ for (const listener of all) {
48
+ const owners = owned.filter((socket) => socket.port === listener.port &&
49
+ (socket.address === listener.address || (ANY_ADDRESS.has(socket.address) && ANY_ADDRESS.has(listener.address))));
50
+ if (owners.length === 0)
51
+ merged.push(listener);
52
+ for (const owner of owners) {
53
+ used.add(owner);
54
+ merged.push({ ...listener, pid: owner.pid });
55
+ }
56
+ }
57
+ for (const socket of owned) {
58
+ if (used.has(socket))
59
+ continue;
60
+ merged.push({ address: socket.address === "*" ? "0.0.0.0" : socket.address, port: socket.port, pid: socket.pid });
61
+ }
62
+ return merged;
63
+ }
64
+ export class MacosPortTableAdapter {
65
+ processRunner;
66
+ constructor(processRunner) {
67
+ this.processRunner = processRunner;
68
+ }
69
+ async listeners(signal) {
70
+ const options = { signal, timeoutMs: 30_000 };
71
+ const [netstat, lsof] = await Promise.all([
72
+ this.processRunner.run("/usr/sbin/netstat", ["-an", "-p", "tcp"], options),
73
+ this.processRunner.run("/usr/sbin/lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-Fpn"], options),
74
+ ]);
75
+ // lsof exits 1 when it finds none of the user's sockets.
76
+ if (netstat.code !== 0 && lsof.code > 1) {
77
+ const detail = netstat.stderr.trim().split("\n")[0] ?? "";
78
+ throw new CapabilityUnavailableError("core.ports.failed", { program: "netstat", code: netstat.code, detail });
79
+ }
80
+ return mergeListeners(netstat.code === 0 ? parseMacosNetstat(netstat.stdout) : [], parseLsofListeners(lsof.stdout));
81
+ }
82
+ canListen(port) {
83
+ return canListen(port);
84
+ }
85
+ }
86
+ //# sourceMappingURL=macos-port-table.adapter.js.map