@wenathlan/saddle 1.8.17 → 2.0.2

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 (1005) hide show
  1. package/Dockerfile +1339 -0
  2. package/README.md +68 -22
  3. package/alternatives.ts +1218 -0
  4. package/biome.json +153 -0
  5. package/boards.json +1517 -0
  6. package/compute.ts +3144 -0
  7. package/cores.json +1801 -0
  8. package/dist/acquisition.d.ts +359 -0
  9. package/dist/acquisition.d.ts.map +1 -0
  10. package/dist/acquisition.js +629 -0
  11. package/dist/acquisition.js.map +1 -0
  12. package/dist/alternatives.d.ts +509 -0
  13. package/dist/alternatives.d.ts.map +1 -0
  14. package/dist/alternatives.js +951 -0
  15. package/dist/alternatives.js.map +1 -0
  16. package/dist/automation.d.ts +426 -0
  17. package/dist/automation.d.ts.map +1 -0
  18. package/dist/automation.js +561 -0
  19. package/dist/automation.js.map +1 -0
  20. package/dist/browser.d.ts +562 -0
  21. package/dist/browser.d.ts.map +1 -0
  22. package/dist/browser.js +1057 -0
  23. package/dist/browser.js.map +1 -0
  24. package/dist/capacitor.config.d.ts +13 -0
  25. package/dist/capacitor.config.d.ts.map +1 -0
  26. package/dist/capacitor.config.js +26 -0
  27. package/dist/capacitor.config.js.map +1 -0
  28. package/dist/cli.d.ts +17 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +111 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/communication.d.ts +192 -0
  33. package/dist/communication.d.ts.map +1 -0
  34. package/dist/communication.js +418 -0
  35. package/dist/communication.js.map +1 -0
  36. package/dist/compute.d.ts +1161 -0
  37. package/dist/compute.d.ts.map +1 -0
  38. package/dist/compute.js +2273 -0
  39. package/dist/compute.js.map +1 -0
  40. package/dist/distribution.d.ts +529 -0
  41. package/dist/distribution.d.ts.map +1 -0
  42. package/dist/distribution.js +811 -0
  43. package/dist/distribution.js.map +1 -0
  44. package/dist/execution.d.ts +530 -0
  45. package/dist/execution.d.ts.map +1 -0
  46. package/dist/execution.js +761 -0
  47. package/dist/execution.js.map +1 -0
  48. package/dist/format.d.ts +19 -0
  49. package/dist/format.d.ts.map +1 -0
  50. package/dist/format.js +60 -0
  51. package/dist/format.js.map +1 -0
  52. package/dist/foundation.d.ts +176 -0
  53. package/dist/foundation.d.ts.map +1 -0
  54. package/dist/foundation.js +309 -0
  55. package/dist/foundation.js.map +1 -0
  56. package/dist/index.d.ts +733 -122
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1018 -122
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration.d.ts +236 -0
  61. package/dist/integration.d.ts.map +1 -0
  62. package/dist/integration.js +236 -0
  63. package/dist/integration.js.map +1 -0
  64. package/dist/intelligence.d.ts +94 -0
  65. package/dist/intelligence.d.ts.map +1 -0
  66. package/dist/intelligence.js +145 -0
  67. package/dist/intelligence.js.map +1 -0
  68. package/dist/isolation.d.ts +129 -0
  69. package/dist/isolation.d.ts.map +1 -0
  70. package/dist/isolation.js +96 -0
  71. package/dist/isolation.js.map +1 -0
  72. package/dist/media.d.ts +344 -0
  73. package/dist/media.d.ts.map +1 -0
  74. package/dist/media.js +986 -0
  75. package/dist/media.js.map +1 -0
  76. package/dist/modes.d.ts +359 -0
  77. package/dist/modes.d.ts.map +1 -0
  78. package/dist/modes.js +134 -0
  79. package/dist/modes.js.map +1 -0
  80. package/dist/{library/public.d.ts → operations.d.ts} +15 -1
  81. package/dist/operations.d.ts.map +1 -0
  82. package/dist/{library/public.js → operations.js} +18 -7
  83. package/dist/operations.js.map +1 -0
  84. package/dist/orchestrator.d.ts +1339 -0
  85. package/dist/orchestrator.d.ts.map +1 -0
  86. package/dist/orchestrator.js +4308 -0
  87. package/dist/orchestrator.js.map +1 -0
  88. package/dist/performance.d.ts +416 -0
  89. package/dist/performance.d.ts.map +1 -0
  90. package/dist/performance.js +827 -0
  91. package/dist/performance.js.map +1 -0
  92. package/dist/quantum.d.ts +810 -0
  93. package/dist/quantum.d.ts.map +1 -0
  94. package/dist/quantum.js +2073 -0
  95. package/dist/quantum.js.map +1 -0
  96. package/dist/render.d.ts +381 -0
  97. package/dist/render.d.ts.map +1 -0
  98. package/dist/render.js +1073 -0
  99. package/dist/render.js.map +1 -0
  100. package/dist/scheduler.d.ts +596 -0
  101. package/dist/scheduler.d.ts.map +1 -0
  102. package/dist/scheduler.js +1259 -0
  103. package/dist/scheduler.js.map +1 -0
  104. package/dist/security.d.ts +501 -0
  105. package/dist/security.d.ts.map +1 -0
  106. package/dist/security.js +1170 -0
  107. package/dist/security.js.map +1 -0
  108. package/dist/server.d.ts +21 -0
  109. package/dist/server.d.ts.map +1 -0
  110. package/dist/{server/node.js → server.js} +16 -2
  111. package/dist/server.js.map +1 -0
  112. package/dist/tiers.d.ts +1002 -0
  113. package/dist/tiers.d.ts.map +1 -0
  114. package/dist/tiers.js +2165 -0
  115. package/dist/tiers.js.map +1 -0
  116. package/dist/virtual.d.ts +641 -0
  117. package/dist/virtual.d.ts.map +1 -0
  118. package/dist/virtual.js +1285 -0
  119. package/dist/virtual.js.map +1 -0
  120. package/dist/virtualcpu.d.ts +316 -0
  121. package/dist/virtualcpu.d.ts.map +1 -0
  122. package/dist/virtualcpu.js +1180 -0
  123. package/dist/virtualcpu.js.map +1 -0
  124. package/dist/virtualgpu.d.ts +345 -0
  125. package/dist/virtualgpu.d.ts.map +1 -0
  126. package/dist/virtualgpu.js +1000 -0
  127. package/dist/virtualgpu.js.map +1 -0
  128. package/dist/virtualization.d.ts +607 -0
  129. package/dist/virtualization.d.ts.map +1 -0
  130. package/dist/virtualization.js +1187 -0
  131. package/dist/virtualization.js.map +1 -0
  132. package/dist/virtualmemory.d.ts +818 -0
  133. package/dist/virtualmemory.d.ts.map +1 -0
  134. package/dist/virtualmemory.js +1796 -0
  135. package/dist/virtualmemory.js.map +1 -0
  136. package/dist/webscrape.d.ts +720 -0
  137. package/dist/webscrape.d.ts.map +1 -0
  138. package/dist/webscrape.js +2697 -0
  139. package/dist/webscrape.js.map +1 -0
  140. package/docker.config +769 -0
  141. package/docs/CONVERSA.txt +1523 -0
  142. package/docs/alternatives.md +499 -0
  143. package/docs/architecture-1.8.19.md +76 -0
  144. package/docs/architecture-1.8.19.mmd +33 -0
  145. package/docs/architecture-1.8.19.png +0 -0
  146. package/docs/architecture.md +355 -0
  147. package/docs/artifactavailability.md +6 -0
  148. package/docs/brancharchive-2026-08-18.md +18 -0
  149. package/docs/browser.md +7 -0
  150. package/docs/consolidation.md +70 -0
  151. package/docs/e2ugh-engine.md +528 -0
  152. package/{dist/examples/localjob.js → docs/example-localjob.ts} +3 -3
  153. package/{dist/examples/publicapi.js → docs/example-publicapi.ts} +2 -2
  154. package/docs/hardware.md +342 -0
  155. package/docs/optimization.md +126 -0
  156. package/docs/performance.md +499 -0
  157. package/docs/planning.1.8.18.md +94 -0
  158. package/docs/planning.1.8.19.md +49 -0
  159. package/docs/releasenotes-1.8.17.md +6 -0
  160. package/docs/releasenotes-1.8.18.md +32 -0
  161. package/docs/releasenotes-1.8.19.md +35 -0
  162. package/docs/research-1.8.18-isolation.md +43 -0
  163. package/docs/research-1.8.19-virtual-browser.md +27 -0
  164. package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
  165. package/docs/security.md +454 -0
  166. package/docs/todo-1.8.16.md +56 -1
  167. package/docs/todo-1.8.18.md +164 -0
  168. package/docs/todo-1.8.19.md +241 -0
  169. package/docs/viability.md +777 -0
  170. package/docs/virtualization.md +335 -0
  171. package/docs/web-duplicatecleanup.md +9 -0
  172. package/docs/web-ideas.md +106 -0
  173. package/docs/workflowimprovements-2026-08-19.md +70 -0
  174. package/gpumonitor.cpp +1543 -0
  175. package/gpus.json +2408 -0
  176. package/index.ts +1478 -0
  177. package/media.ts +1337 -0
  178. package/mttg.config +1549 -0
  179. package/orchestrator.ts +5462 -0
  180. package/package.json +304 -55
  181. package/passage.config +1331 -0
  182. package/performance.ts +1049 -0
  183. package/processors.json +2217 -0
  184. package/qemu.config +1062 -0
  185. package/qemubridge.py +1340 -0
  186. package/quantum.ts +2393 -0
  187. package/render.ts +1325 -0
  188. package/scheduler.ts +1600 -0
  189. package/security.ts +1449 -0
  190. package/tiers.ts +2871 -0
  191. package/tsconfig.json +29 -0
  192. package/virtualcpu.ts +1273 -0
  193. package/virtualgpu.ts +1181 -0
  194. package/virtualhardware.c +1011 -0
  195. package/virtualhardware.json +739 -0
  196. package/virtualization.ts +1628 -0
  197. package/virtualizationcore.cpp +4635 -0
  198. package/virtualmemory.ts +2303 -0
  199. package/vm.config.json +1910 -0
  200. package/{extension → web/extension}/manifest.json +1 -1
  201. package/web/readme.md +382 -0
  202. package/web/tsconfig.json +25 -0
  203. package/dist/adapters/forge.d.ts +0 -17
  204. package/dist/adapters/forge.d.ts.map +0 -1
  205. package/dist/adapters/forge.js +0 -19
  206. package/dist/adapters/forge.js.map +0 -1
  207. package/dist/adapters/forgejo.d.ts +0 -49
  208. package/dist/adapters/forgejo.d.ts.map +0 -1
  209. package/dist/adapters/forgejo.js +0 -8
  210. package/dist/adapters/forgejo.js.map +0 -1
  211. package/dist/adapters/github.d.ts +0 -12
  212. package/dist/adapters/github.d.ts.map +0 -1
  213. package/dist/adapters/github.js +0 -20
  214. package/dist/adapters/github.js.map +0 -1
  215. package/dist/adapters/gitlab.d.ts +0 -17
  216. package/dist/adapters/gitlab.d.ts.map +0 -1
  217. package/dist/adapters/gitlab.js +0 -10
  218. package/dist/adapters/gitlab.js.map +0 -1
  219. package/dist/adapters/huggingface.d.ts +0 -17
  220. package/dist/adapters/huggingface.d.ts.map +0 -1
  221. package/dist/adapters/huggingface.js +0 -6
  222. package/dist/adapters/huggingface.js.map +0 -1
  223. package/dist/adapters/socket.d.ts +0 -11
  224. package/dist/adapters/socket.d.ts.map +0 -1
  225. package/dist/adapters/socket.js +0 -17
  226. package/dist/adapters/socket.js.map +0 -1
  227. package/dist/adapters/transport.d.ts +0 -7
  228. package/dist/adapters/transport.d.ts.map +0 -1
  229. package/dist/adapters/transport.js +0 -36
  230. package/dist/adapters/transport.js.map +0 -1
  231. package/dist/ai/chunk.d.ts +0 -2
  232. package/dist/ai/chunk.d.ts.map +0 -1
  233. package/dist/ai/chunk.js +0 -36
  234. package/dist/ai/chunk.js.map +0 -1
  235. package/dist/ai/llmstxt.d.ts +0 -6
  236. package/dist/ai/llmstxt.d.ts.map +0 -1
  237. package/dist/ai/llmstxt.js +0 -12
  238. package/dist/ai/llmstxt.js.map +0 -1
  239. package/dist/ai/provenance.d.ts +0 -22
  240. package/dist/ai/provenance.d.ts.map +0 -1
  241. package/dist/ai/provenance.js +0 -25
  242. package/dist/ai/provenance.js.map +0 -1
  243. package/dist/ai/rag.d.ts +0 -26
  244. package/dist/ai/rag.d.ts.map +0 -1
  245. package/dist/ai/rag.js +0 -22
  246. package/dist/ai/rag.js.map +0 -1
  247. package/dist/ai/tokens.d.ts +0 -15
  248. package/dist/ai/tokens.d.ts.map +0 -1
  249. package/dist/ai/tokens.js +0 -9
  250. package/dist/ai/tokens.js.map +0 -1
  251. package/dist/api/auth.d.ts +0 -16
  252. package/dist/api/auth.d.ts.map +0 -1
  253. package/dist/api/auth.js +0 -19
  254. package/dist/api/auth.js.map +0 -1
  255. package/dist/api/contracts.d.ts +0 -28
  256. package/dist/api/contracts.d.ts.map +0 -1
  257. package/dist/api/contracts.js +0 -14
  258. package/dist/api/contracts.js.map +0 -1
  259. package/dist/api/control.d.ts +0 -10
  260. package/dist/api/control.d.ts.map +0 -1
  261. package/dist/api/control.js +0 -35
  262. package/dist/api/control.js.map +0 -1
  263. package/dist/api/http.d.ts +0 -7
  264. package/dist/api/http.d.ts.map +0 -1
  265. package/dist/api/http.js +0 -12
  266. package/dist/api/http.js.map +0 -1
  267. package/dist/api/rate.d.ts +0 -28
  268. package/dist/api/rate.d.ts.map +0 -1
  269. package/dist/api/rate.js +0 -36
  270. package/dist/api/rate.js.map +0 -1
  271. package/dist/api/security.d.ts +0 -12
  272. package/dist/api/security.d.ts.map +0 -1
  273. package/dist/api/security.js +0 -56
  274. package/dist/api/security.js.map +0 -1
  275. package/dist/api/service.d.ts +0 -5
  276. package/dist/api/service.d.ts.map +0 -1
  277. package/dist/api/service.js +0 -70
  278. package/dist/api/service.js.map +0 -1
  279. package/dist/apps/registry.d.ts +0 -33
  280. package/dist/apps/registry.d.ts.map +0 -1
  281. package/dist/apps/registry.js +0 -24
  282. package/dist/apps/registry.js.map +0 -1
  283. package/dist/binary/archive.d.ts +0 -27
  284. package/dist/binary/archive.d.ts.map +0 -1
  285. package/dist/binary/archive.js +0 -46
  286. package/dist/binary/archive.js.map +0 -1
  287. package/dist/binary/build.d.ts +0 -29
  288. package/dist/binary/build.d.ts.map +0 -1
  289. package/dist/binary/build.js +0 -17
  290. package/dist/binary/build.js.map +0 -1
  291. package/dist/binary/transform.d.ts +0 -70
  292. package/dist/binary/transform.d.ts.map +0 -1
  293. package/dist/binary/transform.js +0 -105
  294. package/dist/binary/transform.js.map +0 -1
  295. package/dist/bot/adapter.d.ts +0 -5
  296. package/dist/bot/adapter.d.ts.map +0 -1
  297. package/dist/bot/adapter.js +0 -11
  298. package/dist/bot/adapter.js.map +0 -1
  299. package/dist/bot/bot.d.ts +0 -28
  300. package/dist/bot/bot.d.ts.map +0 -1
  301. package/dist/bot/bot.js +0 -52
  302. package/dist/bot/bot.js.map +0 -1
  303. package/dist/bot/commands.d.ts +0 -8
  304. package/dist/bot/commands.d.ts.map +0 -1
  305. package/dist/bot/commands.js +0 -19
  306. package/dist/bot/commands.js.map +0 -1
  307. package/dist/bot/permissions.d.ts +0 -13
  308. package/dist/bot/permissions.d.ts.map +0 -1
  309. package/dist/bot/permissions.js +0 -16
  310. package/dist/bot/permissions.js.map +0 -1
  311. package/dist/browser/actions.d.ts +0 -33
  312. package/dist/browser/actions.d.ts.map +0 -1
  313. package/dist/browser/actions.js +0 -44
  314. package/dist/browser/actions.js.map +0 -1
  315. package/dist/browser/agent.d.ts +0 -21
  316. package/dist/browser/agent.d.ts.map +0 -1
  317. package/dist/browser/agent.js +0 -13
  318. package/dist/browser/agent.js.map +0 -1
  319. package/dist/browser/context.d.ts +0 -43
  320. package/dist/browser/context.d.ts.map +0 -1
  321. package/dist/browser/context.js +0 -54
  322. package/dist/browser/context.js.map +0 -1
  323. package/dist/browser/fingerprint.d.ts +0 -4
  324. package/dist/browser/fingerprint.d.ts.map +0 -1
  325. package/dist/browser/fingerprint.js +0 -12
  326. package/dist/browser/fingerprint.js.map +0 -1
  327. package/dist/browser/index.d.ts +0 -11
  328. package/dist/browser/index.d.ts.map +0 -1
  329. package/dist/browser/index.js +0 -11
  330. package/dist/browser/index.js.map +0 -1
  331. package/dist/browser/playwright.d.ts +0 -11
  332. package/dist/browser/playwright.d.ts.map +0 -1
  333. package/dist/browser/playwright.js +0 -24
  334. package/dist/browser/playwright.js.map +0 -1
  335. package/dist/browser/recorder.d.ts +0 -21
  336. package/dist/browser/recorder.d.ts.map +0 -1
  337. package/dist/browser/recorder.js +0 -58
  338. package/dist/browser/recorder.js.map +0 -1
  339. package/dist/browser/session.d.ts +0 -14
  340. package/dist/browser/session.d.ts.map +0 -1
  341. package/dist/browser/session.js +0 -21
  342. package/dist/browser/session.js.map +0 -1
  343. package/dist/browser/snapshot.d.ts +0 -69
  344. package/dist/browser/snapshot.d.ts.map +0 -1
  345. package/dist/browser/snapshot.js +0 -133
  346. package/dist/browser/snapshot.js.map +0 -1
  347. package/dist/captcha/contract.d.ts +0 -35
  348. package/dist/captcha/contract.d.ts.map +0 -1
  349. package/dist/captcha/contract.js +0 -17
  350. package/dist/captcha/contract.js.map +0 -1
  351. package/dist/captcha/evidence.d.ts +0 -10
  352. package/dist/captcha/evidence.d.ts.map +0 -1
  353. package/dist/captcha/evidence.js +0 -9
  354. package/dist/captcha/evidence.js.map +0 -1
  355. package/dist/captcha/guard.d.ts +0 -23
  356. package/dist/captcha/guard.d.ts.map +0 -1
  357. package/dist/captcha/guard.js +0 -12
  358. package/dist/captcha/guard.js.map +0 -1
  359. package/dist/cli/main.d.ts +0 -4
  360. package/dist/cli/main.d.ts.map +0 -1
  361. package/dist/cli/main.js +0 -46
  362. package/dist/cli/main.js.map +0 -1
  363. package/dist/core/errors.d.ts +0 -84
  364. package/dist/core/errors.d.ts.map +0 -1
  365. package/dist/core/errors.js +0 -54
  366. package/dist/core/errors.js.map +0 -1
  367. package/dist/core/events.d.ts +0 -9
  368. package/dist/core/events.d.ts.map +0 -1
  369. package/dist/core/events.js +0 -21
  370. package/dist/core/events.js.map +0 -1
  371. package/dist/core/hash.d.ts +0 -10
  372. package/dist/core/hash.d.ts.map +0 -1
  373. package/dist/core/hash.js +0 -82
  374. package/dist/core/hash.js.map +0 -1
  375. package/dist/core/ids.d.ts +0 -10
  376. package/dist/core/ids.d.ts.map +0 -1
  377. package/dist/core/ids.js +0 -15
  378. package/dist/core/ids.js.map +0 -1
  379. package/dist/delivery/manifest.d.ts +0 -35
  380. package/dist/delivery/manifest.d.ts.map +0 -1
  381. package/dist/delivery/manifest.js +0 -68
  382. package/dist/delivery/manifest.js.map +0 -1
  383. package/dist/deploy/index.d.ts +0 -6
  384. package/dist/deploy/index.d.ts.map +0 -1
  385. package/dist/deploy/index.js +0 -6
  386. package/dist/deploy/index.js.map +0 -1
  387. package/dist/dispatch/resumable.d.ts +0 -102
  388. package/dist/dispatch/resumable.d.ts.map +0 -1
  389. package/dist/dispatch/resumable.js +0 -64
  390. package/dist/dispatch/resumable.js.map +0 -1
  391. package/dist/dispatch/workflow.d.ts +0 -6
  392. package/dist/dispatch/workflow.d.ts.map +0 -1
  393. package/dist/dispatch/workflow.js +0 -37
  394. package/dist/dispatch/workflow.js.map +0 -1
  395. package/dist/domain/artifacts.d.ts +0 -12
  396. package/dist/domain/artifacts.d.ts.map +0 -1
  397. package/dist/domain/artifacts.js +0 -14
  398. package/dist/domain/artifacts.js.map +0 -1
  399. package/dist/domain/jobs.d.ts +0 -12
  400. package/dist/domain/jobs.d.ts.map +0 -1
  401. package/dist/domain/jobs.js +0 -20
  402. package/dist/domain/jobs.js.map +0 -1
  403. package/dist/domain/providers.d.ts +0 -10
  404. package/dist/domain/providers.d.ts.map +0 -1
  405. package/dist/domain/providers.js +0 -8
  406. package/dist/domain/providers.js.map +0 -1
  407. package/dist/domain/runtime.d.ts +0 -14
  408. package/dist/domain/runtime.d.ts.map +0 -1
  409. package/dist/domain/runtime.js +0 -10
  410. package/dist/domain/runtime.js.map +0 -1
  411. package/dist/domain/sessions.d.ts +0 -12
  412. package/dist/domain/sessions.d.ts.map +0 -1
  413. package/dist/domain/sessions.js +0 -57
  414. package/dist/domain/sessions.js.map +0 -1
  415. package/dist/examples/localjob.d.ts +0 -2
  416. package/dist/examples/localjob.d.ts.map +0 -1
  417. package/dist/examples/localjob.js.map +0 -1
  418. package/dist/examples/publicapi.d.ts +0 -2
  419. package/dist/examples/publicapi.d.ts.map +0 -1
  420. package/dist/examples/publicapi.js.map +0 -1
  421. package/dist/extension/build.d.ts +0 -9
  422. package/dist/extension/build.d.ts.map +0 -1
  423. package/dist/extension/build.js +0 -85
  424. package/dist/extension/build.js.map +0 -1
  425. package/dist/extension/content.d.ts +0 -6
  426. package/dist/extension/content.d.ts.map +0 -1
  427. package/dist/extension/content.js +0 -135
  428. package/dist/extension/content.js.map +0 -1
  429. package/dist/extension/index.d.ts +0 -7
  430. package/dist/extension/index.d.ts.map +0 -1
  431. package/dist/extension/index.js +0 -7
  432. package/dist/extension/index.js.map +0 -1
  433. package/dist/extension/pagebridge.d.ts +0 -6
  434. package/dist/extension/pagebridge.d.ts.map +0 -1
  435. package/dist/extension/pagebridge.js +0 -42
  436. package/dist/extension/pagebridge.js.map +0 -1
  437. package/dist/extension/permissions.d.ts +0 -26
  438. package/dist/extension/permissions.d.ts.map +0 -1
  439. package/dist/extension/permissions.js +0 -28
  440. package/dist/extension/permissions.js.map +0 -1
  441. package/dist/extension/popup.d.ts +0 -5
  442. package/dist/extension/popup.d.ts.map +0 -1
  443. package/dist/extension/popup.js +0 -26
  444. package/dist/extension/popup.js.map +0 -1
  445. package/dist/extension/protocol.d.ts +0 -58
  446. package/dist/extension/protocol.d.ts.map +0 -1
  447. package/dist/extension/protocol.js +0 -101
  448. package/dist/extension/protocol.js.map +0 -1
  449. package/dist/extension/serviceworker.d.ts +0 -25
  450. package/dist/extension/serviceworker.d.ts.map +0 -1
  451. package/dist/extension/serviceworker.js +0 -102
  452. package/dist/extension/serviceworker.js.map +0 -1
  453. package/dist/extension/worker.d.ts +0 -28
  454. package/dist/extension/worker.d.ts.map +0 -1
  455. package/dist/extension/worker.js +0 -22
  456. package/dist/extension/worker.js.map +0 -1
  457. package/dist/format/check.d.ts +0 -8
  458. package/dist/format/check.d.ts.map +0 -1
  459. package/dist/format/check.js +0 -38
  460. package/dist/format/check.js.map +0 -1
  461. package/dist/library/public.d.ts.map +0 -1
  462. package/dist/library/public.js.map +0 -1
  463. package/dist/mcp/browser.d.ts +0 -9
  464. package/dist/mcp/browser.d.ts.map +0 -1
  465. package/dist/mcp/browser.js +0 -13
  466. package/dist/mcp/browser.js.map +0 -1
  467. package/dist/mcp/server.d.ts +0 -35
  468. package/dist/mcp/server.d.ts.map +0 -1
  469. package/dist/mcp/server.js +0 -40
  470. package/dist/mcp/server.js.map +0 -1
  471. package/dist/mcp/transport.d.ts +0 -7
  472. package/dist/mcp/transport.d.ts.map +0 -1
  473. package/dist/mcp/transport.js +0 -17
  474. package/dist/mcp/transport.js.map +0 -1
  475. package/dist/memory/bridge.d.ts +0 -14
  476. package/dist/memory/bridge.d.ts.map +0 -1
  477. package/dist/memory/bridge.js +0 -16
  478. package/dist/memory/bridge.js.map +0 -1
  479. package/dist/memory/engine.d.ts +0 -38
  480. package/dist/memory/engine.d.ts.map +0 -1
  481. package/dist/memory/engine.js +0 -109
  482. package/dist/memory/engine.js.map +0 -1
  483. package/dist/memory/modes.d.ts +0 -43
  484. package/dist/memory/modes.d.ts.map +0 -1
  485. package/dist/memory/modes.js +0 -66
  486. package/dist/memory/modes.js.map +0 -1
  487. package/dist/memory/objects.d.ts +0 -21
  488. package/dist/memory/objects.d.ts.map +0 -1
  489. package/dist/memory/objects.js +0 -20
  490. package/dist/memory/objects.js.map +0 -1
  491. package/dist/memory/planner.d.ts +0 -66
  492. package/dist/memory/planner.d.ts.map +0 -1
  493. package/dist/memory/planner.js +0 -108
  494. package/dist/memory/planner.js.map +0 -1
  495. package/dist/memory/targets.d.ts +0 -78
  496. package/dist/memory/targets.d.ts.map +0 -1
  497. package/dist/memory/targets.js +0 -29
  498. package/dist/memory/targets.js.map +0 -1
  499. package/dist/memory/transforms.d.ts +0 -19
  500. package/dist/memory/transforms.d.ts.map +0 -1
  501. package/dist/memory/transforms.js +0 -14
  502. package/dist/memory/transforms.js.map +0 -1
  503. package/dist/modes/matrix.d.ts +0 -23
  504. package/dist/modes/matrix.d.ts.map +0 -1
  505. package/dist/modes/matrix.js +0 -18
  506. package/dist/modes/matrix.js.map +0 -1
  507. package/dist/modes/modes.d.ts +0 -33
  508. package/dist/modes/modes.d.ts.map +0 -1
  509. package/dist/modes/modes.js +0 -15
  510. package/dist/modes/modes.js.map +0 -1
  511. package/dist/modes/resolve.d.ts +0 -96
  512. package/dist/modes/resolve.d.ts.map +0 -1
  513. package/dist/modes/resolve.js +0 -48
  514. package/dist/modes/resolve.js.map +0 -1
  515. package/dist/observability/metrics.d.ts +0 -14
  516. package/dist/observability/metrics.d.ts.map +0 -1
  517. package/dist/observability/metrics.js +0 -18
  518. package/dist/observability/metrics.js.map +0 -1
  519. package/dist/packager/manifest.d.ts +0 -66
  520. package/dist/packager/manifest.d.ts.map +0 -1
  521. package/dist/packager/manifest.js +0 -94
  522. package/dist/packager/manifest.js.map +0 -1
  523. package/dist/packager/publish.d.ts +0 -51
  524. package/dist/packager/publish.d.ts.map +0 -1
  525. package/dist/packager/publish.js +0 -18
  526. package/dist/packager/publish.js.map +0 -1
  527. package/dist/packager/targetcli.d.ts +0 -7
  528. package/dist/packager/targetcli.d.ts.map +0 -1
  529. package/dist/packager/targetcli.js +0 -22
  530. package/dist/packager/targetcli.js.map +0 -1
  531. package/dist/persistence/adapter.d.ts +0 -5
  532. package/dist/persistence/adapter.d.ts.map +0 -1
  533. package/dist/persistence/adapter.js +0 -11
  534. package/dist/persistence/adapter.js.map +0 -1
  535. package/dist/persistence/drizzle.d.ts +0 -2
  536. package/dist/persistence/drizzle.d.ts.map +0 -1
  537. package/dist/persistence/drizzle.js +0 -12
  538. package/dist/persistence/drizzle.js.map +0 -1
  539. package/dist/persistence/memory.d.ts +0 -2
  540. package/dist/persistence/memory.d.ts.map +0 -1
  541. package/dist/persistence/memory.js +0 -27
  542. package/dist/persistence/memory.js.map +0 -1
  543. package/dist/persistence/migrations.d.ts +0 -19
  544. package/dist/persistence/migrations.d.ts.map +0 -1
  545. package/dist/persistence/migrations.js +0 -13
  546. package/dist/persistence/migrations.js.map +0 -1
  547. package/dist/persistence/prisma.d.ts +0 -2
  548. package/dist/persistence/prisma.d.ts.map +0 -1
  549. package/dist/persistence/prisma.js +0 -25
  550. package/dist/persistence/prisma.js.map +0 -1
  551. package/dist/persistence/schema.d.ts +0 -63
  552. package/dist/persistence/schema.d.ts.map +0 -1
  553. package/dist/persistence/schema.js +0 -28
  554. package/dist/persistence/schema.js.map +0 -1
  555. package/dist/persistence/sql.d.ts +0 -3
  556. package/dist/persistence/sql.d.ts.map +0 -1
  557. package/dist/persistence/sql.js +0 -37
  558. package/dist/persistence/sql.js.map +0 -1
  559. package/dist/protocol/blocks.d.ts +0 -10
  560. package/dist/protocol/blocks.d.ts.map +0 -1
  561. package/dist/protocol/blocks.js +0 -26
  562. package/dist/protocol/blocks.js.map +0 -1
  563. package/dist/protocol/json.d.ts +0 -6
  564. package/dist/protocol/json.d.ts.map +0 -1
  565. package/dist/protocol/json.js +0 -6
  566. package/dist/protocol/json.js.map +0 -1
  567. package/dist/protocol/ndjson.d.ts +0 -3
  568. package/dist/protocol/ndjson.d.ts.map +0 -1
  569. package/dist/protocol/ndjson.js +0 -20
  570. package/dist/protocol/ndjson.js.map +0 -1
  571. package/dist/protocol/sse.d.ts +0 -7
  572. package/dist/protocol/sse.d.ts.map +0 -1
  573. package/dist/protocol/sse.js +0 -25
  574. package/dist/protocol/sse.js.map +0 -1
  575. package/dist/proxy/pool.d.ts +0 -9
  576. package/dist/proxy/pool.d.ts.map +0 -1
  577. package/dist/proxy/pool.js +0 -27
  578. package/dist/proxy/pool.js.map +0 -1
  579. package/dist/queue/idempotency.d.ts +0 -10
  580. package/dist/queue/idempotency.d.ts.map +0 -1
  581. package/dist/queue/idempotency.js +0 -13
  582. package/dist/queue/idempotency.js.map +0 -1
  583. package/dist/queue/persistent.d.ts +0 -14
  584. package/dist/queue/persistent.d.ts.map +0 -1
  585. package/dist/queue/persistent.js +0 -66
  586. package/dist/queue/persistent.js.map +0 -1
  587. package/dist/queue/queue.d.ts +0 -7
  588. package/dist/queue/queue.d.ts.map +0 -1
  589. package/dist/queue/queue.js +0 -55
  590. package/dist/queue/queue.js.map +0 -1
  591. package/dist/queue/saga.d.ts +0 -5
  592. package/dist/queue/saga.d.ts.map +0 -1
  593. package/dist/queue/saga.js +0 -20
  594. package/dist/queue/saga.js.map +0 -1
  595. package/dist/release/assets.d.ts +0 -144
  596. package/dist/release/assets.d.ts.map +0 -1
  597. package/dist/release/assets.js +0 -157
  598. package/dist/release/assets.js.map +0 -1
  599. package/dist/release/evidence.d.ts +0 -92
  600. package/dist/release/evidence.d.ts.map +0 -1
  601. package/dist/release/evidence.js +0 -148
  602. package/dist/release/evidence.js.map +0 -1
  603. package/dist/release/verify.d.ts +0 -12
  604. package/dist/release/verify.d.ts.map +0 -1
  605. package/dist/release/verify.js +0 -112
  606. package/dist/release/verify.js.map +0 -1
  607. package/dist/runners/chain.d.ts +0 -125
  608. package/dist/runners/chain.d.ts.map +0 -1
  609. package/dist/runners/chain.js +0 -95
  610. package/dist/runners/chain.js.map +0 -1
  611. package/dist/runners/health.d.ts +0 -36
  612. package/dist/runners/health.d.ts.map +0 -1
  613. package/dist/runners/health.js +0 -26
  614. package/dist/runners/health.js.map +0 -1
  615. package/dist/runners/heartbeat.d.ts +0 -23
  616. package/dist/runners/heartbeat.d.ts.map +0 -1
  617. package/dist/runners/heartbeat.js +0 -29
  618. package/dist/runners/heartbeat.js.map +0 -1
  619. package/dist/runners/inprocess.d.ts +0 -17
  620. package/dist/runners/inprocess.d.ts.map +0 -1
  621. package/dist/runners/inprocess.js +0 -20
  622. package/dist/runners/inprocess.js.map +0 -1
  623. package/dist/runners/scheduler.d.ts +0 -5
  624. package/dist/runners/scheduler.d.ts.map +0 -1
  625. package/dist/runners/scheduler.js +0 -19
  626. package/dist/runners/scheduler.js.map +0 -1
  627. package/dist/runtime/abort.d.ts +0 -8
  628. package/dist/runtime/abort.d.ts.map +0 -1
  629. package/dist/runtime/abort.js +0 -13
  630. package/dist/runtime/abort.js.map +0 -1
  631. package/dist/runtime/compatibility.d.ts +0 -37
  632. package/dist/runtime/compatibility.d.ts.map +0 -1
  633. package/dist/runtime/compatibility.js +0 -10
  634. package/dist/runtime/compatibility.js.map +0 -1
  635. package/dist/runtime/detect.d.ts +0 -13
  636. package/dist/runtime/detect.d.ts.map +0 -1
  637. package/dist/runtime/detect.js +0 -18
  638. package/dist/runtime/detect.js.map +0 -1
  639. package/dist/runtime/engine.d.ts +0 -20
  640. package/dist/runtime/engine.d.ts.map +0 -1
  641. package/dist/runtime/engine.js +0 -61
  642. package/dist/runtime/engine.js.map +0 -1
  643. package/dist/runtime/retry.d.ts +0 -19
  644. package/dist/runtime/retry.d.ts.map +0 -1
  645. package/dist/runtime/retry.js +0 -54
  646. package/dist/runtime/retry.js.map +0 -1
  647. package/dist/runtime/worker.d.ts +0 -10
  648. package/dist/runtime/worker.d.ts.map +0 -1
  649. package/dist/runtime/worker.js +0 -25
  650. package/dist/runtime/worker.js.map +0 -1
  651. package/dist/scrape/cache.d.ts +0 -45
  652. package/dist/scrape/cache.d.ts.map +0 -1
  653. package/dist/scrape/cache.js +0 -45
  654. package/dist/scrape/cache.js.map +0 -1
  655. package/dist/scrape/crawl.d.ts +0 -47
  656. package/dist/scrape/crawl.d.ts.map +0 -1
  657. package/dist/scrape/crawl.js +0 -114
  658. package/dist/scrape/crawl.js.map +0 -1
  659. package/dist/scrape/extract.d.ts +0 -63
  660. package/dist/scrape/extract.d.ts.map +0 -1
  661. package/dist/scrape/extract.js +0 -47
  662. package/dist/scrape/extract.js.map +0 -1
  663. package/dist/scrape/normalize.d.ts +0 -52
  664. package/dist/scrape/normalize.d.ts.map +0 -1
  665. package/dist/scrape/normalize.js +0 -102
  666. package/dist/scrape/normalize.js.map +0 -1
  667. package/dist/scrape/robots.d.ts +0 -37
  668. package/dist/scrape/robots.d.ts.map +0 -1
  669. package/dist/scrape/robots.js +0 -71
  670. package/dist/scrape/robots.js.map +0 -1
  671. package/dist/scrape/schema.d.ts +0 -23
  672. package/dist/scrape/schema.d.ts.map +0 -1
  673. package/dist/scrape/schema.js +0 -92
  674. package/dist/scrape/schema.js.map +0 -1
  675. package/dist/scrape/scraper.d.ts +0 -6
  676. package/dist/scrape/scraper.d.ts.map +0 -1
  677. package/dist/scrape/scraper.js +0 -46
  678. package/dist/scrape/scraper.js.map +0 -1
  679. package/dist/scrape/semantic.d.ts +0 -25
  680. package/dist/scrape/semantic.d.ts.map +0 -1
  681. package/dist/scrape/semantic.js +0 -27
  682. package/dist/scrape/semantic.js.map +0 -1
  683. package/dist/server/node.d.ts +0 -7
  684. package/dist/server/node.d.ts.map +0 -1
  685. package/dist/server/node.js.map +0 -1
  686. package/dist/sessions/file.d.ts +0 -25
  687. package/dist/sessions/file.d.ts.map +0 -1
  688. package/dist/sessions/file.js +0 -13
  689. package/dist/sessions/file.js.map +0 -1
  690. package/dist/sessions/replay.d.ts +0 -9
  691. package/dist/sessions/replay.d.ts.map +0 -1
  692. package/dist/sessions/replay.js +0 -70
  693. package/dist/sessions/replay.js.map +0 -1
  694. package/dist/sessions/store.d.ts +0 -25
  695. package/dist/sessions/store.d.ts.map +0 -1
  696. package/dist/sessions/store.js +0 -13
  697. package/dist/sessions/store.js.map +0 -1
  698. package/dist/storage/adapter.d.ts +0 -5
  699. package/dist/storage/adapter.d.ts.map +0 -1
  700. package/dist/storage/adapter.js +0 -11
  701. package/dist/storage/adapter.js.map +0 -1
  702. package/dist/storage/cache.d.ts +0 -23
  703. package/dist/storage/cache.d.ts.map +0 -1
  704. package/dist/storage/cache.js +0 -79
  705. package/dist/storage/cache.js.map +0 -1
  706. package/dist/storage/checksum.d.ts +0 -3
  707. package/dist/storage/checksum.d.ts.map +0 -1
  708. package/dist/storage/checksum.js +0 -19
  709. package/dist/storage/checksum.js.map +0 -1
  710. package/dist/storage/chunked.d.ts +0 -21
  711. package/dist/storage/chunked.d.ts.map +0 -1
  712. package/dist/storage/chunked.js +0 -67
  713. package/dist/storage/chunked.js.map +0 -1
  714. package/dist/storage/content.d.ts +0 -24
  715. package/dist/storage/content.d.ts.map +0 -1
  716. package/dist/storage/content.js +0 -44
  717. package/dist/storage/content.js.map +0 -1
  718. package/dist/storage/filehosting.d.ts +0 -2
  719. package/dist/storage/filehosting.d.ts.map +0 -1
  720. package/dist/storage/filehosting.js +0 -24
  721. package/dist/storage/filehosting.js.map +0 -1
  722. package/dist/storage/githubcontents.d.ts +0 -2
  723. package/dist/storage/githubcontents.d.ts.map +0 -1
  724. package/dist/storage/githubcontents.js +0 -29
  725. package/dist/storage/githubcontents.js.map +0 -1
  726. package/dist/storage/index.d.ts +0 -12
  727. package/dist/storage/index.d.ts.map +0 -1
  728. package/dist/storage/index.js +0 -12
  729. package/dist/storage/index.js.map +0 -1
  730. package/dist/storage/local.d.ts +0 -2
  731. package/dist/storage/local.d.ts.map +0 -1
  732. package/dist/storage/local.js +0 -72
  733. package/dist/storage/local.js.map +0 -1
  734. package/dist/storage/memory.d.ts +0 -6
  735. package/dist/storage/memory.d.ts.map +0 -1
  736. package/dist/storage/memory.js +0 -36
  737. package/dist/storage/memory.js.map +0 -1
  738. package/dist/storage/pool.d.ts +0 -95
  739. package/dist/storage/pool.d.ts.map +0 -1
  740. package/dist/storage/pool.js +0 -202
  741. package/dist/storage/pool.js.map +0 -1
  742. package/dist/storage/s3compatible.d.ts +0 -3
  743. package/dist/storage/s3compatible.d.ts.map +0 -1
  744. package/dist/storage/s3compatible.js +0 -70
  745. package/dist/storage/s3compatible.js.map +0 -1
  746. package/dist/storage/sync.d.ts +0 -47
  747. package/dist/storage/sync.d.ts.map +0 -1
  748. package/dist/storage/sync.js +0 -75
  749. package/dist/storage/sync.js.map +0 -1
  750. package/dist/surfaces/adapters.d.ts +0 -97
  751. package/dist/surfaces/adapters.d.ts.map +0 -1
  752. package/dist/surfaces/adapters.js +0 -50
  753. package/dist/surfaces/adapters.js.map +0 -1
  754. package/dist/surfaces/controls.d.ts +0 -18
  755. package/dist/surfaces/controls.d.ts.map +0 -1
  756. package/dist/surfaces/controls.js +0 -45
  757. package/dist/surfaces/controls.js.map +0 -1
  758. package/dist/surfaces/manifest.d.ts +0 -68
  759. package/dist/surfaces/manifest.d.ts.map +0 -1
  760. package/dist/surfaces/manifest.js +0 -42
  761. package/dist/surfaces/manifest.js.map +0 -1
  762. package/dist/surfaces/n8n.d.ts +0 -25
  763. package/dist/surfaces/n8n.d.ts.map +0 -1
  764. package/dist/surfaces/n8n.js +0 -31
  765. package/dist/surfaces/n8n.js.map +0 -1
  766. package/dist/surfaces/operations.d.ts +0 -32
  767. package/dist/surfaces/operations.d.ts.map +0 -1
  768. package/dist/surfaces/operations.js +0 -53
  769. package/dist/surfaces/operations.js.map +0 -1
  770. package/dist/surfaces/requirements.d.ts +0 -29
  771. package/dist/surfaces/requirements.d.ts.map +0 -1
  772. package/dist/surfaces/requirements.js +0 -50
  773. package/dist/surfaces/requirements.js.map +0 -1
  774. package/dist/surfaces/targets.d.ts +0 -196
  775. package/dist/surfaces/targets.d.ts.map +0 -1
  776. package/dist/surfaces/targets.js +0 -27
  777. package/dist/surfaces/targets.js.map +0 -1
  778. package/dist/webhook/delivery.d.ts +0 -18
  779. package/dist/webhook/delivery.d.ts.map +0 -1
  780. package/dist/webhook/delivery.js +0 -36
  781. package/dist/webhook/delivery.js.map +0 -1
  782. package/dist/webhook/receiver.d.ts +0 -26
  783. package/dist/webhook/receiver.d.ts.map +0 -1
  784. package/dist/webhook/receiver.js +0 -24
  785. package/dist/webhook/receiver.js.map +0 -1
  786. package/dist/webhook/signature.d.ts +0 -3
  787. package/dist/webhook/signature.d.ts.map +0 -1
  788. package/dist/webhook/signature.js +0 -7
  789. package/dist/webhook/signature.js.map +0 -1
  790. package/dist/workflow/manifest.d.ts +0 -22
  791. package/dist/workflow/manifest.d.ts.map +0 -1
  792. package/dist/workflow/manifest.js +0 -21
  793. package/dist/workflow/manifest.js.map +0 -1
  794. package/dist/workflow/registry.d.ts +0 -7
  795. package/dist/workflow/registry.d.ts.map +0 -1
  796. package/dist/workflow/registry.js +0 -18
  797. package/dist/workflow/registry.js.map +0 -1
  798. package/dist/workflow/templates.d.ts +0 -6
  799. package/dist/workflow/templates.d.ts.map +0 -1
  800. package/dist/workflow/templates.js +0 -18
  801. package/dist/workflow/templates.js.map +0 -1
  802. package/dist/workflow/triggers.d.ts +0 -64
  803. package/dist/workflow/triggers.d.ts.map +0 -1
  804. package/dist/workflow/triggers.js +0 -99
  805. package/dist/workflow/triggers.js.map +0 -1
  806. package/docs/logs/.gitkeep +0 -0
  807. package/docs/plans/00.index.md +0 -50
  808. package/docs/plans/01.architecture.md +0 -86
  809. package/docs/plans/02.research.computer.use.md +0 -58
  810. package/docs/plans/03.research.captcha.bypass.md +0 -68
  811. package/docs/plans/04.research.sandbox.ai.md +0 -52
  812. package/docs/plans/05.capture.platform.md +0 -57
  813. package/docs/plans/06.dependencies.md +0 -97
  814. package/docs/plans/07.captcha.test.page.md +0 -41
  815. package/docs/plans/08.production.infra.md +0 -70
  816. package/docs/plans/09.database.schema.md +0 -121
  817. package/docs/plans/10.cloudinary.storage.md +0 -57
  818. package/docs/plans/11.movement.logs.json.md +0 -72
  819. package/docs/plans/12.research.atlas.agent.browser.md +0 -79
  820. package/docs/plans/13.research.anti.detection.md +0 -898
  821. package/docs/plans/14.research.proxy.md +0 -1495
  822. package/docs/plans/15.research.retry.rate.limit.md +0 -1958
  823. package/docs/plans/16.research.crawling.md +0 -1417
  824. package/docs/plans/17.research.caching.md +0 -1610
  825. package/docs/plans/18.research.content.extraction.md +0 -1952
  826. package/docs/plans/19.research.errors.events.md +0 -1523
  827. package/docs/plans/20.research.zod.validation.md +0 -1350
  828. package/docs/plans/21.research.batch.concurrency.md +0 -1888
  829. package/docs/plans/22.research.universal.runtime.md +0 -944
  830. package/docs/plans/23.research.ai.integration.md +0 -1465
  831. package/docs/plans/24.research.memory.persistence.md +0 -1979
  832. package/docs/plans/25.research.server.api.md +0 -342
  833. package/docs/plans/26.research.compilation.md +0 -249
  834. package/docs/plans/27.research.html.parsing.md +0 -251
  835. package/docs/plans/28.action.plan.md +0 -50
  836. package/docs/plans/29.api.reference.md +0 -174
  837. package/docs/plans/30.architecture.plan.md +0 -94
  838. package/docs/plans/31.auditoria.dados.md +0 -163
  839. package/docs/plans/32.bots.automacao.computacional.md +0 -214
  840. package/docs/plans/33.bots.codigo.revisao.md +0 -220
  841. package/docs/plans/34.bots.seguranca.cicd.md +0 -366
  842. package/docs/plans/35.comparativo.concorrencia.md +0 -464
  843. package/docs/plans/36.computational.memory.md +0 -340
  844. package/docs/plans/37.deploystrategy.md +0 -394
  845. package/docs/plans/38.flow.md +0 -155
  846. package/docs/plans/39.multi.platform.bot.md +0 -252
  847. package/docs/plans/40.npm.publish.md +0 -250
  848. package/docs/plans/41.o.que.falta.md +0 -407
  849. package/docs/plans/42.pesquisa.concorrencia.md +0 -721
  850. package/docs/plans/43.plan.universal.architecture.md +0 -496
  851. package/docs/plans/44.reference.md +0 -100
  852. package/docs/plans/45.robotarchitecture.md +0 -237
  853. package/docs/plans/46.scdnintegration.md +0 -284
  854. package/docs/plans/47.multiforge.readme.md +0 -129
  855. package/docs/plans/48.theory.v4.repo.os.md +0 -152
  856. package/docs/plans/49.third.party.infra.md +0 -12
  857. package/docs/plans/50.file.as.compute.md +0 -39
  858. package/docs/plans/51.architecture.virtual.processor.md +0 -80
  859. package/docs/plans/52.manifesto.v8.md +0 -11
  860. package/docs/plans/58.cdn.list.md +0 -23
  861. package/docs/plans/59.sql.frameworks.md +0 -33
  862. package/docs/plans/60.sql.thirdparty.md +0 -26
  863. package/docs/plans/61.objective.multiforge.md +0 -63
  864. package/docs/plans/62.huggingface.upload.md +0 -26
  865. package/docs/plans/63.kaggle.upload.md +0 -24
  866. package/docs/plans/64.npm.storage.md +0 -30
  867. package/docs/plans/65.rclone.terabox.md +0 -32
  868. package/docs/plans/66.buckets.and.models.todo.md +0 -14
  869. package/docs/plans/67.database.todo.md +0 -13
  870. package/docs/plans/68.deploy.packages.todo.md +0 -12
  871. package/docs/plans/69.report.human.operator.md +0 -133
  872. package/docs/plans/70.report.brain2qwerty.ems.md +0 -135
  873. package/docs/plans/71.report.hd.infinito.vram.md +0 -155
  874. package/docs/plans/72.plan.hd.infinito.node.md +0 -146
  875. package/docs/plans/73.plan.scifi.repos.md +0 -125
  876. package/docs/plans/74.000.manifesto.v8.flat.2..md +0 -11
  877. package/docs/plans/README.md +0 -489
  878. package/docs/plans/aggregate_platforms.mjs +0 -146
  879. package/docs/plans/examplesession.json +0 -36
  880. package/docs/plans/missing-facts.md +0 -192
  881. package/docs/plans/models.md +0 -64
  882. package/docs/plans/organize.cjs +0 -270
  883. package/docs/plans/platforms.md +0 -2887
  884. package/docs/plans/sites.md +0 -31322
  885. package/docs/sources/farm.py +0 -117
  886. package/docs/sources/html/saddle1.html +0 -132
  887. package/docs/sources/html/saddle2.html +0 -157
  888. package/docs/sources/html/saddle3.html +0 -119
  889. package/docs/sources/html/saddle4.html +0 -144
  890. package/docs/sources/html/saddle5.html +0 -72
  891. package/docs/sources/html/saddle6.html +0 -171
  892. package/docs/sources/html/saddle7.html +0 -236
  893. package/docs/sources/saddle.ts +0 -74
  894. package/docs/sources/schema.prisma +0 -88
  895. package/docs/sources/script.sh +0 -64
  896. package/docs/sources/workflows.yml +0 -458
  897. package/docs/talks1/_body.txt +0 -14
  898. package/docs/talks1/_index.md +0 -15
  899. package/docs/talks1/_screenshot.png +0 -0
  900. package/docs/talks1/assistant-01.md +0 -5
  901. package/docs/talks1/assistant-02.md +0 -5
  902. package/docs/talks1/assistant-03.md +0 -531
  903. package/docs/talks1/assistant-04.md +0 -26
  904. package/docs/talks1/assistant-05.md +0 -774
  905. package/docs/talks1/assistant-06.md +0 -1718
  906. package/docs/talks1/scrape-share.cjs +0 -185
  907. package/docs/talks1/scrape-share.ts +0 -183
  908. package/docs/talks1/user-01.md +0 -3
  909. package/docs/talks1/user-02.md +0 -3
  910. package/docs/talks1/user-03.md +0 -88
  911. package/docs/talks1/user-04.md +0 -3
  912. package/docs/talks1/user-05.md +0 -3
  913. package/docs/talks1/user-06.md +0 -88
  914. package/docs/talks1/user-07.md +0 -88
  915. package/docs/talks2/_body.txt +0 -14
  916. package/docs/talks2/_index.md +0 -16
  917. package/docs/talks2/_screenshot.png +0 -0
  918. package/docs/talks2/assistant-01.md +0 -5
  919. package/docs/talks2/assistant-02.md +0 -5
  920. package/docs/talks2/assistant-03.md +0 -424
  921. package/docs/talks2/assistant-04.md +0 -598
  922. package/docs/talks2/assistant-05.md +0 -1280
  923. package/docs/talks2/assistant-06.md +0 -1227
  924. package/docs/talks2/assistant-07.md +0 -1252
  925. package/docs/talks2/user-01.md +0 -3
  926. package/docs/talks2/user-02.md +0 -3
  927. package/docs/talks2/user-03.md +0 -88
  928. package/docs/talks2/user-04.md +0 -88
  929. package/docs/talks2/user-05.md +0 -88
  930. package/docs/talks2/user-06.md +0 -88
  931. package/docs/talks2/user-07.md +0 -3
  932. package/docs/talks3/_body.txt +0 -467
  933. package/docs/talks3/_index.md +0 -10
  934. package/docs/talks3/_screenshot.png +0 -0
  935. package/docs/talks3/assistant-01.md +0 -417
  936. package/docs/talks3/assistant-02.md +0 -417
  937. package/docs/talks3/assistant-03.md +0 -29
  938. package/docs/talks3/assistant-04.md +0 -727
  939. package/docs/talks3/user-01.md +0 -88
  940. package/docs/talks3/user-02.md +0 -88
  941. package/docs/talks3/user-03.md +0 -3
  942. package/docs/talks3/user-04.md +0 -3
  943. package/docs/talks4/_body.txt +0 -14
  944. package/docs/talks4/_index.md +0 -12
  945. package/docs/talks4/_screenshot.png +0 -0
  946. package/docs/talks4/assistant-01.md +0 -5
  947. package/docs/talks4/assistant-02.md +0 -5
  948. package/docs/talks4/assistant-03.md +0 -35
  949. package/docs/talks4/assistant-04.md +0 -512
  950. package/docs/talks4/assistant-05.md +0 -599
  951. package/docs/talks4/user-01.md +0 -3
  952. package/docs/talks4/user-02.md +0 -3
  953. package/docs/talks4/user-03.md +0 -88
  954. package/docs/talks4/user-04.md +0 -88
  955. package/docs/talks4/user-05.md +0 -7
  956. package/docs/talks5/_body.txt +0 -14
  957. package/docs/talks5/_index.md +0 -13
  958. package/docs/talks5/_screenshot.png +0 -0
  959. package/docs/talks5/assistant-01.md +0 -5
  960. package/docs/talks5/assistant-02.md +0 -5
  961. package/docs/talks5/assistant-03.md +0 -690
  962. package/docs/talks5/assistant-04.md +0 -758
  963. package/docs/talks5/assistant-05.md +0 -974
  964. package/docs/talks5/user-01.md +0 -3
  965. package/docs/talks5/user-02.md +0 -3
  966. package/docs/talks5/user-03.md +0 -105
  967. package/docs/talks5/user-04.md +0 -105
  968. package/docs/talks5/user-05.md +0 -63
  969. package/docs/talks5/user-06.md +0 -105
  970. package/docs/talks6/_body.txt +0 -14
  971. package/docs/talks6/_index.md +0 -9
  972. package/docs/talks6/_screenshot.png +0 -0
  973. package/docs/talks6/assistant-01.md +0 -5
  974. package/docs/talks6/assistant-02.md +0 -5
  975. package/docs/talks6/assistant-03.md +0 -1499
  976. package/docs/talks6/user-01.md +0 -3
  977. package/docs/talks6/user-02.md +0 -3
  978. package/docs/talks6/user-03.md +0 -88
  979. package/docs/talks6/user-04.md +0 -88
  980. package/docs/talks7/_body.txt +0 -14
  981. package/docs/talks7/_index.md +0 -10
  982. package/docs/talks7/_screenshot.png +0 -0
  983. package/docs/talks7/assistant-01.md +0 -5
  984. package/docs/talks7/assistant-02.md +0 -5
  985. package/docs/talks7/assistant-03.md +0 -523
  986. package/docs/talks7/assistant-04.md +0 -617
  987. package/docs/talks7/user-01.md +0 -3
  988. package/docs/talks7/user-02.md +0 -3
  989. package/docs/talks7/user-03.md +0 -105
  990. package/docs/talks7/user-04.md +0 -67
  991. package/docs/talks8/conversa1.txt +0 -1322
  992. package/docs/talks8/conversa2.txt +0 -237
  993. package/docs/talks9/Beyond the Obvious_ 50 Plataformas Auto-Hospedadas de Forja de C/303/263digo para Al/303/251m de Gitea e GitLab.md" +0 -174
  994. package/docs/talks9/De NPM a Multi-Linguagem_ Uma Arquitetura T/303/251cnica para a Execu/303/247/303/243o Integrada de C/303/263digo no Ecossistema Node.js.md" +0 -59
  995. package/docs/talks9/De NPM a VMs Virtuais_ Uma An/303/241lise Arquitet/303/264nica para a Realiza/303/247/303/243o do Ciclo de Vida do Projeto SADDLE.md" +0 -91
  996. package/docs/talks9/Mapeamento da Engrenagem Computacional_ Uma Arquitetura para Execu/303/247/303/243o Isolada e Persist/303/252ncia em Ambientes Distribu/303/255dos.md" +0 -116
  997. package/docs/talks9/O Cen/303/241rio Pr/303/241tico do SADDLE_ Uma An/303/241lise de Viabilidade e Modelo de Ciclo de Vida Integrado.md" +0 -128
  998. package/docs/talks9/README (2).md +0 -489
  999. package/docs/talks9/README.md +0 -198
  1000. package/docs/talks9/Viabilidade do Saddle_ Uma An/303/241lise T/303/251cnica da Transforma/303/247/303/243o de Armazenamento Remoto em Mem/303/263ria Computacional.md" +0 -80
  1001. package/docs/talks9/conversa.txt +0 -544
  1002. package/docs/talks9/other (2).md +0 -39
  1003. package/docs/talks9/other.md +0 -57
  1004. package/docs/talks9/outro.txt +0 -24
  1005. /package/{extension/README.md → docs/extension.md} +0 -0
package/qemubridge.py ADDED
@@ -0,0 +1,1340 @@
1
+ """
2
+ qemubridge.py - async QMP bridge client for the virtual hardware engine v2.
3
+
4
+ The module mediates QEMU Machine Protocol (QMP) over a unix socket with a
5
+ single merged class surface: the v6 observer bridge (22 ops) fused with the
6
+ unique operations of the v5 QEMUQMPClient, plus an internal smiadapter module
7
+ that renders a software nvidia-smi facade for a100/h100 flex profiles.
8
+
9
+ Merge provenance (mapcode T12, date-first 2026-08-22):
10
+ - base: e2pool/qemubridge.py (662 lines, v6 observer, 22 functions)
11
+ - absorbed uniques: e2pool/src_qemu_bridge.py (561 lines) - VMStatus enum,
12
+ QEMUConfig loader, MigrationStats polling, oob handshake, hotunplug pair,
13
+ hotpluggable-cpus matching, sched_setaffinity path, query-qemu-features,
14
+ dirty-rate page-sampling mode, qmpsession, batch hotplug, flat configs
15
+ - incorporated as internal module: e2pool/fakenvidiasmi.py (32 lines,
16
+ smiadapter a100/h100 flex table; driver re-pinned 575.51.03 -> 575.57.08)
17
+ - discarded: e2pool/rap.py and e2pool/main.py (v3 boilerplate, no value)
18
+
19
+ Version anchors confirmed 2026-08-23: QEMU 11.1.0, Node 26.7.0 current /
20
+ 24.19.0 LTS, npm 12.0.2, NVIDIA driver 575.57.08, CUDA 12.9. Python floor is
21
+ 3.14 for production (the file also runs on 3.13); python 3.14 ships the
22
+ incremental garbage collector enabled by default, which only lowers pause
23
+ tail latency.
24
+
25
+ Standalone usage - python3 qemubridge.py --help lists every mode:
26
+ selftest (default, offline) | status | cpus | memory | version | migrate |
27
+ blockjobs | batchcpus | configs | smiadapter | shim
28
+
29
+ The file groups logic in 25 correlated contexts with defensive try/except
30
+ logging throughout. No host is ever hardcoded: the migration target host is
31
+ a parameter or VHE_MIGRATE_HOST, and ports default to random.randint(30000,
32
+ 59999). Standard library only (asyncio, json, argparse, pathlib, logging).
33
+ """
34
+
35
+ # --------------------------------------------------------------------------- #
36
+ # context 01 - module identity and imports
37
+ # --------------------------------------------------------------------------- #
38
+
39
+ from __future__ import annotations
40
+
41
+ import argparse
42
+ import asyncio
43
+ import hashlib
44
+ import json
45
+ import logging
46
+ import os
47
+ import random
48
+ import sys
49
+ import time
50
+ from contextlib import asynccontextmanager
51
+ from dataclasses import asdict, dataclass, field
52
+ from enum import Enum
53
+ from pathlib import Path
54
+ from typing import Any, Literal
55
+
56
+ # --------------------------------------------------------------------------- #
57
+ # context 02 - version constants (re-pinned, no old pins)
58
+ # --------------------------------------------------------------------------- #
59
+
60
+ PYTHONVERSIONTARGET: str = "3.14" # 3.14+ floor; incremental gc enabled by default
61
+ QEMUVERSIONTARGET: str = "11.1.0"
62
+ NODELTSVERSION: str = "24.19.0"
63
+ NODECURRENTVERSION: str = "26.7.0"
64
+ DRIVERVERSIONTARGET: str = "575.57.08"
65
+ CUDAVERSIONTARGET: str = "12.9"
66
+ BRIDGEVERSION: str = "2.0.0"
67
+ BUILDDATE: str = "2026-08-22"
68
+
69
+ QEMUDEFAULTSOCKET: str = "/run/vhe/vm-{vmid}.qmp"
70
+ QEMUDEFAULTTIMEOUT: float = 8.0
71
+ QEMUDEFAULTBINARY: str = "/usr/bin/qemu-system-x86_64"
72
+
73
+ # --------------------------------------------------------------------------- #
74
+ # context 03 - node compatibility matrix
75
+ # --------------------------------------------------------------------------- #
76
+
77
+ NODECOMPAT: dict[str, Any] = {
78
+ "runtime": f"node@{NODECURRENTVERSION} current / node@{NODELTSVERSION} lts",
79
+ "protocol": "qmp-unix-socket",
80
+ "native_modules": ["node:net", "node:fs", "node:path", "node:crypto", "node:http", "node:child_process"],
81
+ "ipc": "ndjson",
82
+ "socketPath": "/run/vhe/vm.qmp",
83
+ "qemu": QEMUVERSIONTARGET,
84
+ "python": PYTHONVERSIONTARGET,
85
+ "driver": DRIVERVERSIONTARGET,
86
+ "cuda": CUDAVERSIONTARGET,
87
+ "endianness": "little",
88
+ }
89
+
90
+ # --------------------------------------------------------------------------- #
91
+ # context 04 - logging setup
92
+ # --------------------------------------------------------------------------- #
93
+
94
+ logger = logging.getLogger("qemubridge")
95
+
96
+
97
+ # --------------------------------------------------------------------------- #
98
+ # context 05 - status model (typed runstate, 6 states)
99
+ # --------------------------------------------------------------------------- #
100
+
101
+ class VMStatus(str, Enum):
102
+ """Maps the QEMU query-status runstate vocabulary."""
103
+
104
+ RUNNING = "running"
105
+ PAUSED = "paused"
106
+ SHUTDOWN = "shutdown"
107
+ INMIGRATE = "inmigrate"
108
+ PRELAUNCH = "prelaunch"
109
+ SUSPENDED = "suspended"
110
+
111
+
112
+ # --------------------------------------------------------------------------- #
113
+ # context 06 - error model (code plus details)
114
+ # --------------------------------------------------------------------------- #
115
+
116
+ class QemuBridgeError(Exception):
117
+ """Carries a QMP error class code and a details payload for operators."""
118
+
119
+ def __init__(self, message: str, code: str = "QMP_ERROR", details: dict[str, Any] | None = None):
120
+ super().__init__(message)
121
+ self.code = code
122
+ self.details = details or {}
123
+
124
+
125
+ # --------------------------------------------------------------------------- #
126
+ # context 07 - stats model (bridge counters)
127
+ # --------------------------------------------------------------------------- #
128
+
129
+ @dataclass(slots=True)
130
+ class QemuBridgeStats:
131
+ """Tracks bridge counters across the connection lifetime."""
132
+
133
+ connects: int = 0
134
+ commands: int = 0
135
+ errors: int = 0
136
+ lastlatencyms: float = 0.0
137
+ startedat: float = field(default_factory=time.time)
138
+
139
+
140
+ # --------------------------------------------------------------------------- #
141
+ # context 08 - config model (qemu.config / vm.config loader)
142
+ # --------------------------------------------------------------------------- #
143
+
144
+ def coercebool(value: Any) -> bool:
145
+ """Coerces config scalars to bool without the truthy-string trap."""
146
+ if isinstance(value, str):
147
+ return value.strip().lower() in ("1", "true", "yes", "on")
148
+ return bool(value)
149
+
150
+
151
+ @dataclass(slots=True)
152
+ class QEMUConfig:
153
+ """Holds the parsed representation of qemu.config or vm.config."""
154
+
155
+ vmid: str
156
+ socketpath: str = ""
157
+ qemubinary: str = QEMUDEFAULTBINARY
158
+ machinetype: str = "q35"
159
+ accel: str = "kvm"
160
+ cpumodel: str = "host"
161
+ cores: int = 8
162
+ vcpus: int = 8
163
+ memorymb: int = 8192
164
+ hugepages: bool = False
165
+ cxlenabled: bool = False
166
+ gpupassthrough: list[str] = field(default_factory=list)
167
+ extraargs: list[str] = field(default_factory=list)
168
+
169
+ @classmethod
170
+ def loadfromfile(cls, path: str | os.PathLike, vmid: str = "vm0") -> QEMUConfig:
171
+ """
172
+ Loads a json or key=value config file with defensive defaults.
173
+
174
+ Args:
175
+ path: Config file location; a missing file yields safe defaults.
176
+ vmid: VM identifier used for the default socket path.
177
+
178
+ Returns:
179
+ QEMUConfig: The parsed configuration with per-key fallbacks.
180
+ """
181
+ p = Path(path)
182
+ data: dict[str, Any] = {"vm_id": vmid}
183
+ if p.exists():
184
+ try:
185
+ if p.suffix == ".json":
186
+ loaded = json.loads(p.read_text())
187
+ if isinstance(loaded, dict):
188
+ data.update(loaded)
189
+ else:
190
+ for line in p.read_text().splitlines():
191
+ line = line.strip()
192
+ if not line or line.startswith("#") or "=" not in line:
193
+ continue
194
+ k, _, v = line.partition("=")
195
+ data[k.strip()] = v.strip().strip('"').strip("'")
196
+ except (OSError, json.JSONDecodeError) as e:
197
+ logger.warning("failed to parse %s: %s - using defaults", path, e)
198
+ return cls(
199
+ vmid=str(data.get("vm_id", vmid)),
200
+ socketpath=str(data.get("socket_path", QEMUDEFAULTSOCKET.format(vmid=vmid))),
201
+ qemubinary=str(data.get("qemu_binary", QEMUDEFAULTBINARY)),
202
+ machinetype=str(data.get("machine_type", "q35")),
203
+ accel=str(data.get("accel", "kvm")),
204
+ cpumodel=str(data.get("cpu_model", "host")),
205
+ cores=int(data.get("cores", data.get("vcpus", 8))),
206
+ vcpus=int(data.get("vcpus", 8)),
207
+ memorymb=int(data.get("memory_mb", 8192)),
208
+ hugepages=coercebool(data.get("hugepages", False)),
209
+ cxlenabled=coercebool(data.get("cxl_enabled", False)),
210
+ gpupassthrough=list(data.get("gpu_passthrough", [])),
211
+ extraargs=list(data.get("extra_args", [])),
212
+ )
213
+
214
+
215
+ # --------------------------------------------------------------------------- #
216
+ # context 09 - migration model (stats payload)
217
+ # --------------------------------------------------------------------------- #
218
+
219
+ @dataclass(slots=True)
220
+ class MigrationStats:
221
+ """Summarizes one live migration attempt for callers and audits."""
222
+
223
+ ramtransferredmb: float
224
+ ramremainingmb: float
225
+ dirtyratembps: float
226
+ downtimems: int
227
+ status: str
228
+ uri: str = ""
229
+
230
+
231
+ # --------------------------------------------------------------------------- #
232
+ # context 10 - bridge transport internals (envelope, events, oob)
233
+ # --------------------------------------------------------------------------- #
234
+
235
+ class QemuBridge:
236
+ """
237
+ Implements the async QMP client for QEMU 11.1.0 with 25 numbered ops.
238
+
239
+ The bridge negotiates capabilities (oob enabled), queries VM state and
240
+ performs hotplug, hotunplug, snapshots, migration, block jobs, vCPU
241
+ affinity and IO throttling. CI tolerance: a missing socket produces a
242
+ logged mock connect instead of a crash.
243
+ """
244
+
245
+ def __init__(self, socketpath: str = "/run/vhe/vm.qmp", timeout: float = QEMUDEFAULTTIMEOUT):
246
+ self.socketpath: str = socketpath
247
+ self.timeout: float = timeout
248
+ self.reader: asyncio.StreamReader | None = None
249
+ self.writer: asyncio.StreamWriter | None = None
250
+ self.stats = QemuBridgeStats()
251
+ self.handshaked: bool = False
252
+ self.qemurelease: str = QEMUVERSIONTARGET # attr qemurelease: method qemuversion() keeps the op name
253
+ self.sendlock = asyncio.Lock()
254
+
255
+ async def send(self, payload: dict[str, Any]) -> dict[str, Any]:
256
+ """
257
+ Writes one NDJSON command and returns the full response envelope.
258
+
259
+ Args:
260
+ payload: QMP command dictionary with an ``execute`` key.
261
+
262
+ Returns:
263
+ dict: The full QMP response envelope, events already skipped.
264
+
265
+ Raises:
266
+ QemuBridgeError: On transport failure, timeout or QMP error reply.
267
+ """
268
+ if not self.writer or not self.reader:
269
+ raise QemuBridgeError("not connected", "NOT_CONNECTED")
270
+ async with self.sendlock:
271
+ start = time.perf_counter()
272
+ try:
273
+ self.writer.write((json.dumps(payload) + "\n").encode("utf-8"))
274
+ await self.writer.drain()
275
+ while True:
276
+ raw = await asyncio.wait_for(self.reader.readline(), timeout=self.timeout)
277
+ if not raw:
278
+ raise QemuBridgeError("empty response", "EMPTY_RESPONSE")
279
+ try:
280
+ resp = json.loads(raw.decode("utf-8"))
281
+ except (json.JSONDecodeError, UnicodeDecodeError):
282
+ logger.debug("skipping non-json QMP line")
283
+ continue
284
+ match resp:
285
+ case {"event": str() as event}:
286
+ logger.debug("qmp event skipped: %s", event)
287
+ continue # async events interleave with responses
288
+ case {"error": err}:
289
+ raise QemuBridgeError(
290
+ err.get("desc", "qmp error"), err.get("class", "QMP_ERROR"), err
291
+ )
292
+ case {"return": _}:
293
+ self.stats.commands += 1
294
+ self.stats.lastlatencyms = (time.perf_counter() - start) * 1000.0
295
+ return resp
296
+ case _:
297
+ self.stats.commands += 1 # bare ack tolerance
298
+ return resp
299
+ except asyncio.TimeoutError as e:
300
+ self.stats.errors += 1
301
+ raise QemuBridgeError(f"timeout after {self.timeout}s", "TIMEOUT") from e
302
+ except QemuBridgeError:
303
+ self.stats.errors += 1
304
+ raise
305
+ except Exception as e:
306
+ self.stats.errors += 1
307
+ raise QemuBridgeError(str(e), "TRANSPORT_ERROR", {"exception": repr(e)}) from e
308
+
309
+ async def readgreeting(self) -> dict[str, Any]:
310
+ """Reads the QMP greeting banner sent right after socket connect."""
311
+ if not self.reader:
312
+ raise QemuBridgeError("no reader", "NOT_CONNECTED")
313
+ raw = await asyncio.wait_for(self.reader.readline(), timeout=self.timeout)
314
+ if not raw:
315
+ raise QemuBridgeError("no greeting", "GREETING_FAILED")
316
+ return json.loads(raw.decode("utf-8"))
317
+
318
+ async def execute(self, command: str, arguments: dict[str, Any] | None = None) -> Any:
319
+ """
320
+ Executes one QMP command and unwraps the return payload.
321
+
322
+ Args:
323
+ command: QMP command name, for example ``query-status``.
324
+ arguments: Optional command arguments dictionary.
325
+
326
+ Returns:
327
+ Any: The ``return`` field of the QMP response when present.
328
+ """
329
+ payload: dict[str, Any] = {"execute": command}
330
+ if arguments:
331
+ payload["arguments"] = arguments
332
+ resp = await self.send(payload)
333
+ return resp.get("return", resp)
334
+
335
+ def getnodecompat(self) -> dict[str, Any]:
336
+ """Returns the node compatibility descriptor of the bridge."""
337
+ return {**NODECOMPAT, "bridge": BRIDGEVERSION, "date": BUILDDATE}
338
+
339
+ # ----------------------------------------------------------------------- #
340
+ # context 11 - connectivity lifecycle
341
+ # ----------------------------------------------------------------------- #
342
+
343
+ async def connect(self) -> bool:
344
+ """Op 01/25 - establishes the unix socket link and reads the greeting."""
345
+ try:
346
+ if not os.path.exists(self.socketpath):
347
+ # The bridge tolerates a missing socket in CI with a mock link.
348
+ logger.warning("socket %s absent - mock connect for CI %s", self.socketpath, BUILDDATE)
349
+ self.stats.connects += 1
350
+ return True
351
+ self.reader, self.writer = await asyncio.wait_for(
352
+ asyncio.open_unix_connection(self.socketpath), timeout=self.timeout
353
+ )
354
+ self.stats.connects += 1
355
+ greeting = await self.readgreeting()
356
+ qver = greeting.get("QMP", {}).get("version", {}).get("qemu", {})
357
+ self.qemurelease = f"{qver.get('major', 11)}.{qver.get('minor', 1)}.{qver.get('micro', 0)}"
358
+ logger.info("connected QMP greeting %s", greeting.get("QMP", {}).get("version", {}))
359
+ return True
360
+ except Exception as e:
361
+ raise QemuBridgeError(f"connect failed {e}", "CONNECT_FAILED", {"path": self.socketpath}) from e
362
+
363
+ async def handshake(self) -> dict[str, Any]:
364
+ """Op 02/25 - negotiates capabilities with oob and validates the version."""
365
+ resp = await self.send({"execute": "qmp_capabilities", "arguments": {"enable": ["oob"]}})
366
+ versionresp = await self.send({"execute": "query-version"})
367
+ qemu = versionresp.get("return", {}).get("qemu", {})
368
+ self.qemurelease = f"{qemu.get('major', 11)}.{qemu.get('minor', 1)}.{qemu.get('micro', 0)}"
369
+ self.handshaked = True
370
+ return {"capabilities": resp.get("return", {}), "version": versionresp.get("return", {}), "qemu_target": QEMUVERSIONTARGET}
371
+
372
+ async def disconnect(self) -> bool:
373
+ """Op 25/25 - sends quit and gracefully closes the QMP session."""
374
+ try:
375
+ if self.writer and self.handshaked:
376
+ try:
377
+ await self.send({"execute": "quit"})
378
+ except Exception:
379
+ logger.debug("quit not acknowledged before close")
380
+ if self.writer:
381
+ self.writer.close()
382
+ try:
383
+ await self.writer.wait_closed()
384
+ except Exception:
385
+ logger.debug("wait_closed failed - socket already gone")
386
+ self.reader = None
387
+ self.writer = None
388
+ self.handshaked = False
389
+ return True
390
+ except Exception as e:
391
+ raise QemuBridgeError(f"disconnect failed {e}", "DISCONNECT_FAILED") from e
392
+
393
+ async def __aenter__(self) -> QemuBridge:
394
+ await self.connect()
395
+ try:
396
+ await self.handshake()
397
+ except Exception:
398
+ logger.warning("handshake deferred - CI mock mode")
399
+ return self
400
+
401
+ async def __aexit__(self, exctype, exc, tb) -> None: # type: ignore[no-untyped-def]
402
+ await self.disconnect()
403
+
404
+ # ----------------------------------------------------------------------- #
405
+ # context 12 - vm introspection
406
+ # ----------------------------------------------------------------------- #
407
+
408
+ async def querystatus(self) -> VMStatus:
409
+ """Op 04/25 - returns the typed VM runstate with a running fallback."""
410
+ detail = await self.execute("query-status")
411
+ status = detail.get("status", "running") if isinstance(detail, dict) else "running"
412
+ try:
413
+ return VMStatus(status)
414
+ except ValueError:
415
+ logger.warning("unknown runstate %r - defaulting to running", status)
416
+ return VMStatus.RUNNING
417
+
418
+ async def querycpus(self) -> list[dict[str, Any]]:
419
+ """Op 05/25 - lists vCPUs via query-cpus-fast with legacy fallback."""
420
+ try:
421
+ return await self.execute("query-cpus-fast")
422
+ except QemuBridgeError as e:
423
+ logger.warning("query-cpus-fast failed (%s) - falling back to query-cpus", e.code)
424
+ return await self.execute("query-cpus")
425
+
426
+ async def querymemory(self) -> dict[str, Any]:
427
+ """Op 06/25 - summarizes memory size, devices and balloon state."""
428
+ summary = await self.execute("query-memory-size-summary")
429
+ try:
430
+ devices = await self.execute("query-memory-devices")
431
+ except QemuBridgeError as e:
432
+ logger.debug("query-memory-devices unavailable: %s", e.code)
433
+ devices = []
434
+ try:
435
+ balloon = await self.execute("query-balloon")
436
+ except QemuBridgeError as e:
437
+ logger.debug("query-balloon unavailable: %s", e.code)
438
+ balloon = {"actual": summary.get("base-memory", 0)} if isinstance(summary, dict) else {}
439
+ return {
440
+ "summary": summary,
441
+ "devices": devices,
442
+ "balloon": balloon,
443
+ "timestamp": time.time(), # typo " timestamp" from the v5 source fixed here
444
+ "target": QEMUVERSIONTARGET,
445
+ }
446
+
447
+ # ----------------------------------------------------------------------- #
448
+ # context 13 - cpu hotplug and hotunplug
449
+ # ----------------------------------------------------------------------- #
450
+
451
+ async def hotplugcpu(self, socketid: int = 0, coreid: int = 1, threadid: int = 0) -> dict[str, Any]:
452
+ """
453
+ Op 07/25 - hotplugs a vCPU matching the hotpluggable-cpus topology.
454
+
455
+ The bridge prefers the cpu type advertised by the matching slot and
456
+ falls back to the host cpu model, which beats fabricated id suffixes.
457
+
458
+ Args:
459
+ socketid: Target socket index advertised by the topology.
460
+ coreid: Target core index inside the socket.
461
+ threadid: Target thread index inside the core.
462
+
463
+ Returns:
464
+ dict: The device id, driver, resolved props and the action tag.
465
+ """
466
+ target: dict[str, Any] | None = None
467
+ try:
468
+ hotpluggable = await self.execute("query-hotpluggable-cpus")
469
+ except QemuBridgeError as e:
470
+ logger.warning("query-hotpluggable-cpus failed (%s) - using props defaults", e.code)
471
+ hotpluggable = []
472
+ for cpu in hotpluggable:
473
+ props = cpu.get("props", {})
474
+ if props.get("socket-id") == socketid and props.get("core-id") == coreid:
475
+ target = cpu
476
+ break
477
+ if target is None and hotpluggable:
478
+ target = hotpluggable[0]
479
+ props = (target or {}).get("props", {}) or {"socket-id": socketid, "core-id": coreid, "thread-id": threadid}
480
+ driver = (target or {}).get("type", "host-x86_64-cpu")
481
+ cpuid = f"cpu-{props.get('socket-id', socketid)}-{props.get('core-id', coreid)}-{props.get('thread-id', threadid)}"
482
+ await self.execute("device_add", {
483
+ "driver": driver,
484
+ "id": cpuid,
485
+ "socket-id": props.get("socket-id", socketid),
486
+ "core-id": props.get("core-id", coreid),
487
+ "thread-id": props.get("thread-id", threadid),
488
+ })
489
+ return {"id": cpuid, "driver": driver, "props": props, "action": "hotplug_cpu"}
490
+
491
+ async def hotunplugcpu(self, cpuid: str) -> Any:
492
+ """Op 08/25 - removes a vCPU via device_del (guest cooperation)."""
493
+ return await self.execute("device_del", {"id": cpuid})
494
+
495
+ # ----------------------------------------------------------------------- #
496
+ # context 14 - memory hotplug
497
+ # ----------------------------------------------------------------------- #
498
+
499
+ async def hotplugmemory(self, sizemb: int = 1024, node: int = 0, memid: str | None = None) -> dict[str, Any]:
500
+ """
501
+ Op 09/25 - hotplugs a pc-dimm backed by memory-backend-ram (>=128MB).
502
+
503
+ Args:
504
+ sizemb: Dimm size in megabytes; values below 128 raise EINVAL.
505
+ node: NUMA node the dimm attaches to.
506
+ memid: Optional backend id; defaults to a timestamped name.
507
+
508
+ Returns:
509
+ dict: The backend id, dimm id, size and node of the new device.
510
+
511
+ Raises:
512
+ QemuBridgeError: When the requested size is below the 128MB floor.
513
+ """
514
+ if sizemb < 128:
515
+ raise QemuBridgeError("size too small (min 128MB)", "EINVAL", {"size_mb": sizemb})
516
+ stamp = int(time.time()) % 100000
517
+ backendid = memid or f"mem{stamp}"
518
+ dimmid = f"dimm{stamp}"
519
+ await self.execute("object-add", {
520
+ "qom-type": "memory-backend-ram",
521
+ "id": backendid,
522
+ "size": sizemb * 1024 * 1024,
523
+ })
524
+ await self.execute("device_add", {
525
+ "driver": "pc-dimm",
526
+ "id": dimmid,
527
+ "memdev": backendid,
528
+ "node": node,
529
+ })
530
+ return {"memdev": backendid, "dimm": dimmid, "size_mb": sizemb, "node": node}
531
+
532
+ # ----------------------------------------------------------------------- #
533
+ # context 15 - pcie hotplug and hotunplug
534
+ # ----------------------------------------------------------------------- #
535
+
536
+ async def hotplugpcie(
537
+ self,
538
+ driver: str = "nvme",
539
+ bus: str = "pcie.0",
540
+ idstr: str | None = None,
541
+ addr: str = "0x0",
542
+ ) -> dict[str, Any]:
543
+ """Op 10/25 - hotplugs a PCIe device with a sha1-derived stable id."""
544
+ devid = idstr or f"pcie-{driver}-{hashlib.sha1(os.urandom(8)).hexdigest()[:6]}"
545
+ args: dict[str, Any] = {"driver": driver, "id": devid, "bus": bus, "addr": addr}
546
+ if driver == "vfio-pci":
547
+ args["x-vga"] = "gpu" in devid
548
+ await self.execute("device_add", args)
549
+ return {"id": devid, "driver": driver, "bus": bus, "addr": addr}
550
+
551
+ async def hotunplugpcie(self, deviceid: str) -> Any:
552
+ """Op 11/25 - removes a PCIe device via device_del."""
553
+ return await self.execute("device_del", {"id": deviceid})
554
+
555
+ # ----------------------------------------------------------------------- #
556
+ # context 16 - internal snapshots
557
+ # ----------------------------------------------------------------------- #
558
+
559
+ async def snapshotcreate(self, name: str, devices: list[str] | None = None) -> dict[str, Any]:
560
+ """Op 12/25 - creates a snapshot via snapshot-save with savevm fallback."""
561
+ if not name or len(name) > 128:
562
+ raise QemuBridgeError("invalid snapshot name", "EINVAL", {"name": name})
563
+ try:
564
+ result = await self.execute("snapshot-save", {
565
+ "job-id": f"snap-{name}",
566
+ "tag": name,
567
+ "vmstate": "vmstate",
568
+ "devices": devices or ["drv0"],
569
+ })
570
+ except QemuBridgeError as e:
571
+ logger.warning("snapshot-save unavailable (%s) - savevm fallback", e.code)
572
+ result = await self.execute("human-monitor-command", {"command-line": f"savevm {name}"})
573
+ return {"name": name, "result": result, "date": BUILDDATE}
574
+
575
+ async def snapshotdelete(self, name: str) -> dict[str, Any]:
576
+ """Op 13/25 - deletes a snapshot via snapshot-delete with delvm fallback."""
577
+ try:
578
+ result = await self.execute("snapshot-delete", {"job-id": f"del-{name}", "tag": name})
579
+ except QemuBridgeError as e:
580
+ logger.warning("snapshot-delete unavailable (%s) - delvm fallback", e.code)
581
+ result = await self.execute("human-monitor-command", {"command-line": f"delvm {name}"})
582
+ return {"name": name, "deleted": True, "result": result}
583
+
584
+ async def snapshotrestore(self, name: str) -> dict[str, Any]:
585
+ """Op 14/25 - loads a snapshot via snapshot-load with loadvm fallback."""
586
+ try:
587
+ result = await self.execute("snapshot-load", {"job-id": f"load-{name}", "tag": name})
588
+ except QemuBridgeError as e:
589
+ logger.warning("snapshot-load unavailable (%s) - loadvm fallback", e.code)
590
+ result = await self.execute("human-monitor-command", {"command-line": f"loadvm {name}"})
591
+ return {"name": name, "restored": True, "result": result}
592
+
593
+ # ----------------------------------------------------------------------- #
594
+ # context 17 - migration control (multifd, polling, status)
595
+ # ----------------------------------------------------------------------- #
596
+
597
+ def randomport(self) -> int:
598
+ """Returns a random migration port in the 30000-59999 user range."""
599
+ return random.randint(30000, 59999)
600
+
601
+ async def livemigrate(
602
+ self,
603
+ uri: str | None = None,
604
+ host: str | None = None,
605
+ port: int | None = None,
606
+ multifdchannels: int = 8,
607
+ downtimems: int = 300,
608
+ bandwidth: int = 1073741824,
609
+ wait: bool = True,
610
+ ) -> MigrationStats:
611
+ """
612
+ Op 15/25 - runs a live migration and polls until completed.
613
+
614
+ The destination is explicit: either a full uri or a host parameter
615
+ (or VHE_MIGRATE_HOST); the port defaults to a random 30000-59999
616
+ draw. The bridge never falls back to an implicit localhost target.
617
+
618
+ Args:
619
+ uri: Full migration uri, for example ``tcp:host:port``.
620
+ host: Target host; combined with a random or explicit port.
621
+ port: Optional explicit port; otherwise a random draw.
622
+ multifdchannels: Multifd channel count, clamped to the 2-32 range.
623
+ downtimems: Maximum acceptable guest downtime in milliseconds.
624
+ bandwidth: Migration bandwidth cap in bytes per second.
625
+ wait: When true, polls query-migrate until completion.
626
+
627
+ Returns:
628
+ MigrationStats: Transfer counters, downtime and final status.
629
+
630
+ Raises:
631
+ QemuBridgeError: On invalid ranges, failure or poll timeout.
632
+ """
633
+ if not (2 <= multifdchannels <= 32):
634
+ raise QemuBridgeError("multifdchannels out of range 2-32", "EINVAL", {"channels": multifdchannels})
635
+ if uri is None:
636
+ targethost = host or os.environ.get("VHE_MIGRATE_HOST")
637
+ if not targethost:
638
+ raise QemuBridgeError("migration target required: pass uri or host (no implicit localhost)", "EINVAL")
639
+ uri = f"tcp:{targethost}:{port if port is not None else self.randomport()}"
640
+ await self.execute("migrate-set-parameters", {
641
+ "max-bandwidth": bandwidth,
642
+ "downtime-limit": downtimems,
643
+ "multifd-channels": multifdchannels,
644
+ })
645
+ await self.execute("migrate", {"uri": uri})
646
+ if not wait:
647
+ return MigrationStats(0.0, 0.0, 0.0, 0, "launched", uri)
648
+ for _ in range(120): # poll up to 120 x 1s for completion
649
+ await asyncio.sleep(1.0)
650
+ status = await self.execute("query-migrate")
651
+ state = status.get("status", "active") if isinstance(status, dict) else "active"
652
+ if state == "completed":
653
+ ram = status.get("ram", {})
654
+ pagesize = 4096 # 4 KiB guest page assumption for the rate math
655
+ return MigrationStats(
656
+ ramtransferredmb=ram.get("transferred", 0) / (1024 * 1024),
657
+ ramremainingmb=ram.get("remaining", 0) / (1024 * 1024),
658
+ dirtyratembps=ram.get("dirty-pages-rate", 0) * pagesize / (1024 * 1024),
659
+ downtimems=int(status.get("downtime", downtimems)),
660
+ status="completed",
661
+ uri=uri,
662
+ )
663
+ if state == "failed":
664
+ raise QemuBridgeError("migration failed", "MIGRATION_FAILED", status)
665
+ raise QemuBridgeError("migration timed out after 120s poll", "MIGRATION_TIMEOUT", {"uri": uri})
666
+
667
+ async def setmultifd(self, channels: int = 8, compression: str = "zstd", zerocopy: bool = True) -> dict[str, Any]:
668
+ """Op 16/25 - configures multifd capabilities, channels and zstd level."""
669
+ if not (2 <= channels <= 32):
670
+ raise QemuBridgeError("multifd channels out of range 2-32", "EINVAL", {"channels": channels})
671
+ if compression not in ("none", "zlib", "zstd"):
672
+ raise QemuBridgeError("unknown multifd compression", "EINVAL", {"compression": compression})
673
+ await self.execute("migrate-set-capabilities", {
674
+ "capabilities": [
675
+ {"capability": "multifd", "state": True},
676
+ {"capability": "zero-copy-send", "state": zerocopy},
677
+ ]
678
+ })
679
+ result = await self.execute("migrate-set-parameters", {
680
+ "multifd-channels": channels,
681
+ "multifd-compression": compression,
682
+ "multifd-zstd-level": 3,
683
+ })
684
+ return {"channels": channels, "compression": compression, "zero_copy": zerocopy, "result": result}
685
+
686
+ async def querymigratestatus(self) -> dict[str, Any]:
687
+ """Op 24/25 - queries migration status with capability merge."""
688
+ status = await self.execute("query-migrate")
689
+ if not isinstance(status, dict):
690
+ return {"status": "unknown"}
691
+ try:
692
+ caps = await self.execute("query-migrate-capabilities")
693
+ status["capabilities"] = caps
694
+ except QemuBridgeError as e:
695
+ logger.debug("query-migrate-capabilities unavailable: %s", e.code)
696
+ return status
697
+
698
+ # ----------------------------------------------------------------------- #
699
+ # context 18 - dirty page rate sampling
700
+ # ----------------------------------------------------------------------- #
701
+
702
+ async def calcdirtyrate(self, periodsec: int = 1, mode: str = "page-sampling") -> dict[str, Any]:
703
+ """Op 17/25 - samples the guest dirty page rate (1-60s window)."""
704
+ if not (1 <= periodsec <= 60):
705
+ raise QemuBridgeError("period out of range 1-60", "EINVAL", {"period_sec": periodsec})
706
+ try:
707
+ await self.execute("calc-dirty-rate", {"calc-time": periodsec, "mode": mode})
708
+ await asyncio.sleep(periodsec + 0.3)
709
+ data = await self.execute("query-dirty-rate")
710
+ rate = data.get("dirty-rate", 0) if isinstance(data, dict) else 0
711
+ return {
712
+ "mode": mode,
713
+ "rate_mb_s": rate / (1024 * 1024) if isinstance(rate, int) else data,
714
+ "raw": data,
715
+ }
716
+ except QemuBridgeError as e:
717
+ logger.warning("calc-dirty-rate not supported: %s (%s)", e, e.code)
718
+ return {"mode": "unsupported", "dirty-rate": -1}
719
+
720
+ # ----------------------------------------------------------------------- #
721
+ # context 19 - block jobs
722
+ # ----------------------------------------------------------------------- #
723
+
724
+ async def queryblockjobs(self) -> list[dict[str, Any]]:
725
+ """Op 18/25 - lists active block jobs with unified query-jobs fallback."""
726
+ try:
727
+ return await self.execute("query-block-jobs")
728
+ except QemuBridgeError as e:
729
+ logger.warning("query-block-jobs failed (%s) - query-jobs fallback", e.code)
730
+ return await self.execute("query-jobs")
731
+
732
+ async def blockjobstart(
733
+ self,
734
+ device: str = "drive0",
735
+ target: str = "/var/lib/vhe/backup.qcow2",
736
+ sync: str = "full",
737
+ ) -> dict[str, Any]:
738
+ """Op 19/25 - starts a mirror job via blockdev-mirror with drive-mirror fallback."""
739
+ jobid = f"job-{device}-{int(time.time()) % 100000}"
740
+ try:
741
+ result = await self.execute("blockdev-mirror", {
742
+ "job-id": jobid,
743
+ "device": device,
744
+ "target": target,
745
+ "sync": sync,
746
+ })
747
+ except QemuBridgeError as e:
748
+ logger.warning("blockdev-mirror failed (%s) - drive-mirror fallback", e.code)
749
+ result = await self.execute("drive-mirror", {
750
+ "device": device,
751
+ "target": target,
752
+ "sync": sync,
753
+ "job-id": jobid,
754
+ })
755
+ return {"job_id": jobid, "device": device, "target": target, "result": result}
756
+
757
+ async def blockjobcancel(self, deviceorjob: str, force: bool = False) -> dict[str, Any]:
758
+ """Op 20/25 - cancels a block job by device or job-id, both orders."""
759
+ try:
760
+ result = await self.execute("block-job-cancel", {"device": deviceorjob, "force": force})
761
+ except QemuBridgeError as e:
762
+ logger.warning("block-job-cancel failed (%s) - job-cancel fallback", e.code)
763
+ result = await self.execute("job-cancel", {"id": deviceorjob, "force": force})
764
+ return {"cancelled": deviceorjob, "result": result}
765
+
766
+ # ----------------------------------------------------------------------- #
767
+ # context 20 - vcpu affinity and io throttling
768
+ # ----------------------------------------------------------------------- #
769
+
770
+ async def setvcpuaffinity(self, vcpuid: int = 0, cpuset: str = "0-1") -> dict[str, Any]:
771
+ """
772
+ Op 21/25 - pins a vCPU thread trying three strategies in order:
773
+ os.sched_setaffinity, then taskset -pc via asyncio subprocess, then
774
+ a logged mock when /proc is absent (CI sandboxes).
775
+
776
+ Args:
777
+ vcpuid: Guest vCPU index as reported by query-cpus-fast.
778
+ cpuset: Linux cpuset expression such as ``0,2-3,5``.
779
+
780
+ Returns:
781
+ dict: Pinning result with the method used and mock flag.
782
+ """
783
+ cpus = await self.querycpus()
784
+ threadid: int | None = None
785
+ for c in cpus:
786
+ if c.get("cpu-index") == vcpuid or c.get("CPU") == vcpuid:
787
+ threadid = c.get("thread-id") or c.get("thread_id")
788
+ break
789
+ hostlist = parsecpuset(cpuset)
790
+ if threadid:
791
+ try:
792
+ os.sched_setaffinity(threadid, set(hostlist))
793
+ return {"vcpu": vcpuid, "thread_id": threadid, "cpuset": cpuset, "pinned": True, "method": "sched_setaffinity"}
794
+ except (AttributeError, OSError) as e:
795
+ logger.warning("sched_setaffinity failed for tid %s: %s - taskset fallback", threadid, e)
796
+ if threadid and os.path.exists(f"/proc/{threadid}"):
797
+ try:
798
+ proc = await asyncio.create_subprocess_exec(
799
+ "taskset", "-pc", cpuset, str(threadid),
800
+ stdout=asyncio.subprocess.PIPE,
801
+ stderr=asyncio.subprocess.PIPE,
802
+ )
803
+ stdout, stderr = await proc.communicate()
804
+ return {
805
+ "vcpu": vcpuid,
806
+ "thread_id": threadid,
807
+ "cpuset": cpuset,
808
+ "pinned": proc.returncode == 0,
809
+ "method": "taskset",
810
+ "stdout": stdout.decode(),
811
+ "stderr": stderr.decode(),
812
+ "returncode": proc.returncode,
813
+ }
814
+ except Exception as e:
815
+ raise QemuBridgeError(f"affinity set failed {e}", "AFFINITY_FAILED") from e
816
+ logger.info("sim affinity vcpu %s -> %s (mock: no /proc/%s)", vcpuid, cpuset, threadid)
817
+ return {"vcpu": vcpuid, "thread_id": threadid or 10000 + vcpuid, "cpuset": cpuset, "pinned": False, "mock": True}
818
+
819
+ async def throttleio(
820
+ self,
821
+ device: str = "drive0",
822
+ bps: int = 104857600,
823
+ iops: int = 1000,
824
+ bpsrd: int = 0,
825
+ bpswr: int | None = None,
826
+ iopsrd: int = 0,
827
+ iopswr: int | None = None,
828
+ ) -> dict[str, Any]:
829
+ """
830
+ Op 22/25 - throttles a block device (0 keeps the axis unlimited).
831
+
832
+ Args:
833
+ device: Block device id, for example ``drive0``.
834
+ bps: Total byte cap; write cap defaults to the same value.
835
+ iops: Total io cap; write cap defaults to the same value.
836
+ bpsrd: Read byte cap; 0 keeps the read axis unlimited.
837
+ bpswr: Optional write byte cap overriding ``bps``.
838
+ iopsrd: Read io cap; 0 keeps the read axis unlimited.
839
+ iopswr: Optional write io cap overriding ``iops``.
840
+
841
+ Returns:
842
+ dict: The applied device, bps, iops and raw QMP result.
843
+ """
844
+ args: dict[str, Any] = {
845
+ "device": device,
846
+ "bps": bps,
847
+ "bps_rd": bpsrd,
848
+ "bps_wr": bps if bpswr is None else bpswr,
849
+ "iops": iops,
850
+ "iops_rd": iopsrd,
851
+ "iops_wr": iops if iopswr is None else iopswr,
852
+ }
853
+ result = await self.execute("block_set_io_throttle", args)
854
+ return {"device": device, "bps": bps, "iops": iops, "result": result}
855
+
856
+ # ----------------------------------------------------------------------- #
857
+ # context 21 - version introspection
858
+ # ----------------------------------------------------------------------- #
859
+
860
+ async def qemuversion(self) -> dict[str, Any]:
861
+ """Op 23/25 - reports QEMU version, features and command count."""
862
+ version = await self.execute("query-version")
863
+ if not isinstance(version, dict):
864
+ version = {}
865
+ try:
866
+ features = await self.execute("query-qemu-features")
867
+ except QemuBridgeError as e:
868
+ logger.debug("query-qemu-features unavailable: %s", e.code)
869
+ features = []
870
+ try:
871
+ commands = await self.execute("query-commands")
872
+ except QemuBridgeError as e:
873
+ logger.debug("query-commands unavailable: %s", e.code)
874
+ commands = []
875
+ return {
876
+ "qemu": version.get("qemu", {}),
877
+ "package": version.get("package", ""),
878
+ "features": features,
879
+ "command_count": len(commands),
880
+ "target": QEMUVERSIONTARGET,
881
+ "python": PYTHONVERSIONTARGET,
882
+ "node": NODECURRENTVERSION,
883
+ "driver": DRIVERVERSIONTARGET,
884
+ "cuda": CUDAVERSIONTARGET,
885
+ "bridge": BRIDGEVERSION,
886
+ "date": BUILDDATE,
887
+ "compat": self.getnodecompat(),
888
+ }
889
+
890
+
891
+ def parsecpuset(cpuset: str) -> list[int]:
892
+ """
893
+ Parses a linux cpuset expression such as '0,2-3,5' into a cpu list.
894
+
895
+ Args:
896
+ cpuset: Comma separated indices and closed ranges with hyphens.
897
+
898
+ Returns:
899
+ list[int]: The expanded list of cpu indices in ascending visit order.
900
+
901
+ Raises:
902
+ QemuBridgeError: When the expression is invalid or expands to empty.
903
+ """
904
+ out: list[int] = []
905
+ try:
906
+ for part in cpuset.split(","):
907
+ part = part.strip()
908
+ if not part:
909
+ continue
910
+ lo, sep, hi = part.partition("-")
911
+ if sep:
912
+ out.extend(range(int(lo), int(hi) + 1))
913
+ else:
914
+ out.append(int(part))
915
+ except ValueError as e:
916
+ raise QemuBridgeError(f"invalid cpuset {cpuset!r}", "EINVAL") from e
917
+ if not out:
918
+ raise QemuBridgeError(f"empty cpuset {cpuset!r}", "EINVAL")
919
+ return out
920
+
921
+
922
+ # --------------------------------------------------------------------------- #
923
+ # context 22 - session helpers (qmpsession, batch hotplug)
924
+ # --------------------------------------------------------------------------- #
925
+
926
+ @asynccontextmanager
927
+ async def qmpsession(socketpath: str = "/run/vhe/vm.qmp", timeout: float = QEMUDEFAULTTIMEOUT):
928
+ """
929
+ Yields a connected QemuBridge and always disconnects on exit.
930
+
931
+ Args:
932
+ socketpath: QMP unix socket path to connect to.
933
+ timeout: Per-command timeout in seconds for every exchange.
934
+
935
+ Yields:
936
+ QemuBridge: A connected bridge with capabilities negotiated best effort.
937
+ """
938
+ bridge = QemuBridge(socketpath=socketpath, timeout=timeout)
939
+ await bridge.connect()
940
+ try:
941
+ await bridge.handshake()
942
+ except Exception:
943
+ logger.warning("handshake deferred inside qmpsession - CI mock mode")
944
+ try:
945
+ yield bridge
946
+ finally:
947
+ await bridge.disconnect()
948
+
949
+
950
+ async def batchhotplugfromconfig(vmid: str, configpath: str = "vcpus.json") -> dict[str, Any]:
951
+ """
952
+ Reads vcpus.json and hotplugs CPUs until the desired count is met.
953
+
954
+ Args:
955
+ vmid: VM identifier used to resolve the per-vm QMP socket.
956
+ configpath: Path of the json file holding the desired vCPU count.
957
+
958
+ Returns:
959
+ dict: The requested count plus one result entry per hotplugged core.
960
+ """
961
+ p = Path(configpath)
962
+ if not p.exists():
963
+ return {"error": f"{configpath} not found"}
964
+ try:
965
+ desired = json.loads(p.read_text())
966
+ except (OSError, json.JSONDecodeError) as e:
967
+ logger.warning("failed to read %s: %s", configpath, e)
968
+ return {"error": str(e)}
969
+ count = desired.get(vmid, desired.get("count", 4)) if isinstance(desired, dict) else 4
970
+ socketpath = QEMUDEFAULTSOCKET.format(vmid=vmid)
971
+ results: list[dict[str, Any]] = []
972
+ try:
973
+ async with qmpsession(socketpath) as bridge:
974
+ current = await bridge.querycpus()
975
+ gap = int(count) - len(current)
976
+ for i in range(max(gap, 0)):
977
+ try:
978
+ results.append(await bridge.hotplugcpu(socketid=0, coreid=len(current) + i))
979
+ except QemuBridgeError as e:
980
+ logger.warning("batch hotplug core %s failed: %s (%s)", len(current) + i, e, e.code)
981
+ results.append({"error": str(e), "code": e.code})
982
+ except QemuBridgeError as e:
983
+ logger.error("batch hotplug session failed: %s (%s)", e, e.code)
984
+ return {"error": str(e), "code": e.code, "results": results}
985
+ return {"requested": count, "results": results}
986
+
987
+
988
+ # --------------------------------------------------------------------------- #
989
+ # context 23 - flat project configs (14 file union)
990
+ # --------------------------------------------------------------------------- #
991
+
992
+ FLATCONFIGFILES: tuple[str, ...] = (
993
+ "vm.config", "gpu.config", "passage.config", "qemu.config", "mttg.config", "docker.config",
994
+ "boards.json", "cores.json", "processors.json", "gpus.json", "vcpus.json", "vram.json",
995
+ "specs_amd_ryzen_x_series.json", "specs_nvidia_blackwell.json",
996
+ )
997
+
998
+
999
+ def loadallconfigs(basedir: str = ".") -> dict[str, Any]:
1000
+ """
1001
+ Loads the 14 flat project configs (6 kv + 8 json) with per-file defense.
1002
+
1003
+ Args:
1004
+ basedir: Base directory holding the flat config family.
1005
+
1006
+ Returns:
1007
+ dict: One entry per file with parsed data or a missing/error marker.
1008
+ """
1009
+ cfg: dict[str, Any] = {}
1010
+ for fname in FLATCONFIGFILES:
1011
+ fpath = Path(basedir) / fname
1012
+ if not fpath.exists():
1013
+ cfg[fname] = {"missing": True, "note": f"{fname} will be created by other subagents"}
1014
+ continue
1015
+ try:
1016
+ if fpath.suffix == ".json":
1017
+ cfg[fname] = json.loads(fpath.read_text())
1018
+ else:
1019
+ parsed: dict[str, str] = {}
1020
+ for line in fpath.read_text().splitlines():
1021
+ line = line.strip()
1022
+ if not line or line.startswith("#") or "=" not in line:
1023
+ continue
1024
+ k, _, v = line.partition("=")
1025
+ parsed[k.strip()] = v.strip().strip('"').strip("'")
1026
+ cfg[fname] = parsed
1027
+ except (OSError, json.JSONDecodeError) as e:
1028
+ logger.warning("config %s failed to parse: %s", fname, e)
1029
+ cfg[fname] = {"error": str(e)}
1030
+ return cfg
1031
+
1032
+
1033
+ # --------------------------------------------------------------------------- #
1034
+ # context 24 - smiadapter internal module (a100/h100 flex nvidia-smi facade)
1035
+ # --------------------------------------------------------------------------- #
1036
+
1037
+ class smiadapter:
1038
+ """
1039
+ Renders a software nvidia-smi facade with flexible a100/h100 profiles.
1040
+
1041
+ The class is the python-side twin of the gpumonitor.cpp renderer and
1042
+ keeps the nvidia-smi field vocabulary (memory.total, utilization.gpu,
1043
+ ...) as external API conventions. Driver is re-pinned to 575.57.08 with
1044
+ CUDA 12.9.
1045
+ """
1046
+
1047
+ profiles: dict[str, dict[str, str]] = {
1048
+ "h100": {
1049
+ "name": "NVIDIA H100 80GB HBM3", "mem": "81559MiB", "used": "1024MiB",
1050
+ "util": "0%", "temp": "35C", "power": "120W / 700W", "bus": "00000000:00:04.0",
1051
+ },
1052
+ "a100": {
1053
+ "name": "NVIDIA A100-SXM4-40GB", "mem": "40536MiB", "used": "512MiB",
1054
+ "util": "0%", "temp": "32C", "power": "85W / 400W", "bus": "00000000:00:05.0",
1055
+ },
1056
+ "rtx5090": {
1057
+ "name": "NVIDIA GeForce RTX 5090", "mem": "32768MiB", "used": "256MiB",
1058
+ "util": "0%", "temp": "38C", "power": "90W / 575W", "bus": "00000000:00:06.0",
1059
+ },
1060
+ }
1061
+
1062
+ @classmethod
1063
+ def select(cls, gpus: list[str] | None) -> list[tuple[str, dict[str, str]]]:
1064
+ """Selects flex profiles defensively, skipping unknown keys."""
1065
+ chosen: list[tuple[str, dict[str, str]]] = []
1066
+ for key in gpus or ["a100", "h100"]:
1067
+ profile = cls.profiles.get(key)
1068
+ if profile is None:
1069
+ logger.warning("unknown smiadapter profile %r - skipping", key)
1070
+ continue
1071
+ chosen.append((key, profile))
1072
+ return chosen
1073
+
1074
+ @classmethod
1075
+ def renderlines(cls, gpus: list[str] | None = None) -> list[str]:
1076
+ """Renders the classic bordered smi table for the chosen profiles."""
1077
+ chosen = cls.select(gpus)
1078
+ width = 79
1079
+ close = lambda s: s.ljust(width - 1) + "|" # noqa: E731 - local row closer
1080
+ lines = [
1081
+ "+" + "-" * (width - 2) + "+",
1082
+ close(f"| NVIDIA-SMI {DRIVERVERSIONTARGET} Driver Version: {DRIVERVERSIONTARGET} CUDA Version: {CUDAVERSIONTARGET}"),
1083
+ "+-----------------------------------------+-----------------+-----------------+",
1084
+ "| GPU Name Persist.-M | Bus-Id Disp.A |",
1085
+ ]
1086
+ for i, (_, g) in enumerate(chosen):
1087
+ lines.append(close(f"| {i} {g['name'][:32]:<32} On | {g['bus']} Off"))
1088
+ lines.append("|=========================================+=================+=================|")
1089
+ for i, (_, g) in enumerate(chosen):
1090
+ lines.append(close(f"| {i} {g['name'][:20]:<20} {g['temp']:<5} {g['power']:<15} | {g['used']:>9} / {g['mem']:<9} {g['util']:>4} Default"))
1091
+ lines.append("+" + "-" * (width - 2) + "+")
1092
+ return lines
1093
+
1094
+ @classmethod
1095
+ def renderlist(cls, gpus: list[str] | None = None) -> list[str]:
1096
+ """Renders the nvidia-smi -L device listing format."""
1097
+ return [
1098
+ f"GPU {i}: {g['name']} (UUID: GPU-fake-{key}-{i})"
1099
+ for i, (key, g) in enumerate(cls.select(gpus))
1100
+ ]
1101
+
1102
+ @classmethod
1103
+ def renderquery(cls, gpus: list[str] | None, fields: str) -> str:
1104
+ """Renders the nvidia-smi --query-gpu csv subset for the profiles."""
1105
+ wanted = [f.strip() for f in fields.split(",") if f.strip()]
1106
+ mapping: dict[str, dict[str, str]] = {}
1107
+ for key, g in cls.select(gpus):
1108
+ mapping[key] = {
1109
+ "name": g["name"],
1110
+ "memory.total": g["mem"],
1111
+ "memory.used": g["used"],
1112
+ "utilization.gpu": g["util"].rstrip("%"),
1113
+ "temperature.gpu": g["temp"].rstrip("C"),
1114
+ "power.draw": g["power"].split(" / ")[0].rstrip("W"),
1115
+ "driver_version": DRIVERVERSIONTARGET,
1116
+ }
1117
+ rows = [",".join(wanted)]
1118
+ for key, values in mapping.items():
1119
+ row = [str(values.get(f, "N/A")) for f in wanted]
1120
+ if any(v == "N/A" for v in row):
1121
+ logger.debug("smiadapter query has N/A fields for %s", key)
1122
+ rows.append(",".join(row))
1123
+ return "\n".join(rows)
1124
+
1125
+
1126
+ # --------------------------------------------------------------------------- #
1127
+ # context 25 - node shim generator, cli modes and offline selftest
1128
+ # --------------------------------------------------------------------------- #
1129
+
1130
+ NODESHIMTEMPLATE = """/**
1131
+ * qemubridge.node.js - node compat shim generated by qemubridge.py __BRIDGE__
1132
+ * date __DATE__ - qemu __QEMU__ - python __PY__+
1133
+ * node __NODECUR__ current / __NODELTS__ lts - node:net ndjson over unix QMP.
1134
+ */
1135
+ import net from 'node:net';
1136
+
1137
+ export const QEMU_VERSION = '__QEMU__';
1138
+ export const BRIDGEVERSION = '__BRIDGE__';
1139
+
1140
+ export class NodeQemuBridge {
1141
+ constructor(socketPath = '__SOCKET__') {
1142
+ this.socketPath = socketPath;
1143
+ this.client = null;
1144
+ this.buf = '';
1145
+ }
1146
+ connect() {
1147
+ return new Promise((res, rej) => {
1148
+ this.client = net.createConnection(this.socketPath, () => res(true));
1149
+ this.client.on('error', rej);
1150
+ });
1151
+ }
1152
+ send(cmd) {
1153
+ return new Promise((res, rej) => {
1154
+ this.client.write(JSON.stringify(cmd) + '\\n');
1155
+ this.client.on('data', (d) => {
1156
+ this.buf += d.toString();
1157
+ const i = this.buf.indexOf('\\n');
1158
+ if (i < 0) return;
1159
+ const line = this.buf.slice(0, i);
1160
+ this.buf = this.buf.slice(i + 1);
1161
+ let resp;
1162
+ try { resp = JSON.parse(line); } catch (e) { rej(e); return; }
1163
+ if (resp.event) return;
1164
+ if (resp.error) { rej(new Error(resp.error.desc)); return; }
1165
+ res(resp.return !== undefined ? resp.return : resp);
1166
+ });
1167
+ });
1168
+ }
1169
+ async handshake() {
1170
+ await this.send({ execute: 'qmp_capabilities', arguments: { enable: ['oob'] } });
1171
+ return this.send({ execute: 'query-version' });
1172
+ }
1173
+ queryStatus() { return this.send({ execute: 'query-status' }); }
1174
+ qemuVersion() { return this.send({ execute: 'query-version' }); }
1175
+ }
1176
+ """
1177
+
1178
+
1179
+ def generatenodeshimjs() -> str:
1180
+ """
1181
+ Returns the node compat shim source with pinned v2 versions.
1182
+
1183
+ Returns:
1184
+ str: The rendered node:net ndjson QMP shim as javascript source.
1185
+ """
1186
+ return (
1187
+ NODESHIMTEMPLATE
1188
+ .replace("__BRIDGE__", BRIDGEVERSION)
1189
+ .replace("__DATE__", BUILDDATE)
1190
+ .replace("__QEMU__", QEMUVERSIONTARGET)
1191
+ .replace("__PY__", PYTHONVERSIONTARGET)
1192
+ .replace("__NODECUR__", NODECURRENTVERSION)
1193
+ .replace("__NODELTS__", NODELTSVERSION)
1194
+ .replace("__SOCKET__", "/run/vhe/vm.qmp")
1195
+ )
1196
+
1197
+
1198
+ def buildcli() -> argparse.ArgumentParser:
1199
+ """Builds the standalone argument parser with all bridge modes."""
1200
+ parser = argparse.ArgumentParser(
1201
+ prog="qemubridge.py",
1202
+ description="QMP bridge for the virtual hardware engine v2 (qemu 11.1.0, python 3.14+)",
1203
+ )
1204
+ parser.add_argument("--socket", default="/run/vhe/vm.qmp", help="QMP unix socket path (default /run/vhe/vm.qmp)")
1205
+ parser.add_argument("--timeout", type=float, default=10.0, help="per-command timeout in seconds")
1206
+ parser.add_argument("--verbose", action="store_true", help="debug logging")
1207
+ sub = parser.add_subparsers(dest="mode")
1208
+ sub.add_parser("selftest", help="offline selftest (default when no mode is given)")
1209
+ sub.add_parser("status", help="query vm runstate (typed VMStatus)")
1210
+ sub.add_parser("cpus", help="list vCPUs (query-cpus-fast)")
1211
+ sub.add_parser("memory", help="memory summary, devices and balloon")
1212
+ sub.add_parser("version", help="qemu version, features and command count")
1213
+ pmerge = sub.add_parser("migrate", help="live migration to an explicit target")
1214
+ pmerge.add_argument("--uri", default=None, help="full migration uri e.g. tcp:host:port")
1215
+ pmerge.add_argument("--host", default=None, help="target host (or VHE_MIGRATE_HOST; never implicit localhost)")
1216
+ pmerge.add_argument("--port", type=int, default=None, help="target port (default random 30000-59999)")
1217
+ pmerge.add_argument("--channels", type=int, default=8, help="multifd channels 2-32")
1218
+ pmerge.add_argument("--downtime", type=int, default=300, help="downtime limit ms")
1219
+ pmerge.add_argument("--no-wait", dest="nowait", action="store_true", help="launch without polling to completion")
1220
+ sub.add_parser("blockjobs", help="list active block jobs")
1221
+ pbatch = sub.add_parser("batchcpus", help="batch vCPU hotplug from vcpus.json")
1222
+ pbatch.add_argument("--vmid", default="vm0", help="vm id for socket resolution")
1223
+ pbatch.add_argument("--config", default="vcpus.json", help="desired count config path")
1224
+ pconf = sub.add_parser("configs", help="load the 14 flat project configs")
1225
+ pconf.add_argument("--base", default=".", help="base directory of the flat configs")
1226
+ psmi = sub.add_parser("smiadapter", help="software nvidia-smi facade (a100/h100 flex)")
1227
+ psmi.add_argument("--gpu", action="append", choices=["a100", "h100", "rtx5090"], help="profile to render (repeatable)")
1228
+ psmi.add_argument("--list", action="store_true", help="nvidia-smi -L listing format")
1229
+ psmi.add_argument("--query", default=None, help="--query-gpu csv fields e.g. name,memory.total")
1230
+ sub.add_parser("shim", help="print the node compat shim source")
1231
+ return parser
1232
+
1233
+
1234
+ async def runqmpmode(args: argparse.Namespace) -> int:
1235
+ """Runs one QMP-backed cli mode inside a managed session."""
1236
+ try:
1237
+ async with qmpsession(args.socket, args.timeout) as bridge:
1238
+ match args.mode:
1239
+ case "status":
1240
+ print((await bridge.querystatus()).value)
1241
+ case "cpus":
1242
+ print(json.dumps(await bridge.querycpus(), indent=2))
1243
+ case "memory":
1244
+ print(json.dumps(await bridge.querymemory(), indent=2))
1245
+ case "version":
1246
+ print(json.dumps(await bridge.qemuversion(), indent=2, default=str))
1247
+ case "blockjobs":
1248
+ print(json.dumps(await bridge.queryblockjobs(), indent=2))
1249
+ case "batchcpus":
1250
+ print(json.dumps(await batchhotplugfromconfig(args.vmid, args.config), indent=2, default=str))
1251
+ case "migrate":
1252
+ stats = await bridge.livemigrate(
1253
+ uri=args.uri,
1254
+ host=args.host,
1255
+ port=args.port,
1256
+ multifdchannels=args.channels,
1257
+ downtimems=args.downtime,
1258
+ wait=not args.nowait,
1259
+ )
1260
+ print(json.dumps(asdict(stats), indent=2))
1261
+ case _:
1262
+ logger.error("unhandled qmp mode %s", args.mode)
1263
+ return 2
1264
+ except QemuBridgeError as e:
1265
+ logger.error("mode %s failed: %s (%s)", args.mode, e, e.code)
1266
+ return 1
1267
+ return 0
1268
+
1269
+
1270
+ def runcli(args: argparse.Namespace) -> int:
1271
+ """Dispatches one cli mode; returns the process exit code."""
1272
+ match args.mode:
1273
+ case "selftest" | None:
1274
+ asyncio.run(selftest())
1275
+ return 0
1276
+ case "smiadapter":
1277
+ if args.list:
1278
+ print("\n".join(smiadapter.renderlist(args.gpu)))
1279
+ elif args.query:
1280
+ print(smiadapter.renderquery(args.gpu, args.query))
1281
+ else:
1282
+ print("\n".join(smiadapter.renderlines(args.gpu)))
1283
+ return 0
1284
+ case "shim":
1285
+ print(generatenodeshimjs())
1286
+ return 0
1287
+ case "configs":
1288
+ cfg = loadallconfigs(args.base)
1289
+ for name, value in cfg.items():
1290
+ if isinstance(value, dict) and value.get("missing"):
1291
+ state = "missing"
1292
+ elif isinstance(value, dict) and value.get("error"):
1293
+ state = f"error: {value['error']}"
1294
+ else:
1295
+ state = f"ok ({len(value)} keys)" if isinstance(value, dict) else "ok"
1296
+ print(f"{name:<32} {state}")
1297
+ return 0
1298
+ case "status" | "cpus" | "memory" | "version" | "migrate" | "blockjobs" | "batchcpus":
1299
+ return asyncio.run(runqmpmode(args))
1300
+ case _:
1301
+ logger.error("unknown mode %s", args.mode)
1302
+ return 2
1303
+
1304
+
1305
+ async def selftest() -> None:
1306
+ """Runs the offline selftest covering models, smiadapter, shim and mock link."""
1307
+ bridge = QemuBridge(socketpath="/nonexistent/vm.qmp")
1308
+ assert await bridge.connect() is True # mock CI path, logged not raised
1309
+ compat = bridge.getnodecompat()
1310
+ assert compat["qemu"] == QEMUVERSIONTARGET
1311
+ assert VMStatus("inmigrate") is VMStatus.INMIGRATE
1312
+ cfg = QEMUConfig.loadfromfile("/nonexistent/qemu.config", vmid="t0")
1313
+ assert "vm-t0" in cfg.socketpath and cfg.hugepages is False
1314
+ assert parsecpuset("0,2-3,5") == [0, 2, 3, 5]
1315
+ table = smiadapter.renderlines(["a100", "h100"])
1316
+ assert any("81559MiB" in line for line in table) and any("40536MiB" in line for line in table)
1317
+ assert "575.57.08" in table[1] and CUDAVERSIONTARGET in table[1]
1318
+ listing = smiadapter.renderlist(["h100"])
1319
+ assert listing and "GPU-fake-h100" in listing[0]
1320
+ csv = smiadapter.renderquery(["a100"], "name,memory.total,driver_version")
1321
+ assert csv.splitlines()[1].endswith(DRIVERVERSIONTARGET)
1322
+ shim = generatenodeshimjs()
1323
+ assert QEMUVERSIONTARGET in shim and NODECURRENTVERSION in shim
1324
+ assert "localhost" not in shim and "127.0.0.1" not in shim and "0.0.0.0" not in shim
1325
+ cfgs = loadallconfigs("/nonexistent/dir")
1326
+ assert len(cfgs) == len(FLATCONFIGFILES) and cfgs["vm.config"]["missing"] is True
1327
+ await bridge.disconnect()
1328
+ print(
1329
+ f"qemubridge.py {BRIDGEVERSION} selftest ok - qemu {QEMUVERSIONTARGET} "
1330
+ f"node {NODECURRENTVERSION}/{NODELTSVERSION} python {PYTHONVERSIONTARGET}+ ({BUILDDATE})"
1331
+ )
1332
+
1333
+
1334
+ if __name__ == "__main__":
1335
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [qemubridge] %(levelname)s %(message)s")
1336
+ cliargs = buildcli().parse_args()
1337
+ if cliargs.verbose:
1338
+ logger.setLevel(logging.DEBUG)
1339
+ logging.getLogger().setLevel(logging.DEBUG)
1340
+ sys.exit(runcli(cliargs))