agents-city 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (449) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +5 -0
  4. package/README.es.md +2226 -0
  5. package/README.md +2196 -0
  6. package/benchmarks/committee/README.md +39 -0
  7. package/benchmarks/committee/metrics.py +51 -0
  8. package/benchmarks/committee/run.py +64 -0
  9. package/benchmarks/committee/traces/01-single.json +8 -0
  10. package/benchmarks/committee/traces/02-mesh.json +16 -0
  11. package/benchmarks/committee/traces/03-chair.json +15 -0
  12. package/benchmarks/latency/README.md +35 -0
  13. package/benchmarks/latency/fake-claude-cli.mjs +119 -0
  14. package/benchmarks/latency/fake-native-server.mjs +340 -0
  15. package/benchmarks/latency/fake-tui.py +91 -0
  16. package/benchmarks/latency/live.py +380 -0
  17. package/benchmarks/stress/README.md +20 -0
  18. package/benchmarks/stress/run.py +487 -0
  19. package/bin/agents +4 -0
  20. package/bin/agents-city.js +119 -0
  21. package/bin/benchmark +17 -0
  22. package/bin/bus +6 -0
  23. package/bin/cities +3 -0
  24. package/bin/city +86 -0
  25. package/bin/committee +6 -0
  26. package/bin/demo +200 -0
  27. package/bin/doctor +3 -0
  28. package/bin/exit +5 -0
  29. package/bin/hall +73 -0
  30. package/bin/hall.html +498 -0
  31. package/bin/logs +3 -0
  32. package/bin/navegador.mjs +243 -0
  33. package/bin/report.py +14 -0
  34. package/bin/reset +3 -0
  35. package/bin/road +3 -0
  36. package/bin/seat +10 -0
  37. package/bin/serve.py +1579 -0
  38. package/bin/setup.py +753 -0
  39. package/bin/shortcut +3 -0
  40. package/bin/skills +3 -0
  41. package/bin/test +133 -0
  42. package/bin/test-actualiza.py +130 -0
  43. package/bin/test-adapter.py +183 -0
  44. package/bin/test-admision.py +99 -0
  45. package/bin/test-atajos.py +301 -0
  46. package/bin/test-avatar.py +58 -0
  47. package/bin/test-benchmark.py +40 -0
  48. package/bin/test-broker.py +334 -0
  49. package/bin/test-cage.py +374 -0
  50. package/bin/test-card.py +402 -0
  51. package/bin/test-channel.py +379 -0
  52. package/bin/test-cities.py +453 -0
  53. package/bin/test-claude-runtime.py +252 -0
  54. package/bin/test-committee.py +340 -0
  55. package/bin/test-contracts.py +551 -0
  56. package/bin/test-crecimiento.py +164 -0
  57. package/bin/test-demo.py +308 -0
  58. package/bin/test-doctor.py +139 -0
  59. package/bin/test-domains.py +151 -0
  60. package/bin/test-evidencia.py +50 -0
  61. package/bin/test-exit.py +128 -0
  62. package/bin/test-hall-protocol.py +93 -0
  63. package/bin/test-launch.py +169 -0
  64. package/bin/test-live-feed.py +508 -0
  65. package/bin/test-navegador.py +140 -0
  66. package/bin/test-pairing.py +99 -0
  67. package/bin/test-parcels.py +283 -0
  68. package/bin/test-runtime-failures.py +252 -0
  69. package/bin/test-runtime-ui.py +488 -0
  70. package/bin/test-runtime.py +268 -0
  71. package/bin/test-rutas.py +80 -0
  72. package/bin/test-seat.py +1598 -0
  73. package/bin/test-security.py +150 -0
  74. package/bin/test-serve.py +1251 -0
  75. package/bin/test-stress.py +64 -0
  76. package/bin/test-widgets.py +143 -0
  77. package/bin/test-workspace.py +251 -0
  78. package/bin/testlib.py +118 -0
  79. package/bin/tokens.py +14 -0
  80. package/bin/update +3 -0
  81. package/bus/scripts/deploy.sh +60 -0
  82. package/bus/scripts/mint-token.sh +68 -0
  83. package/bus/scripts/setup-dev.sh +79 -0
  84. package/bus/scripts/test-channel.ts +8 -0
  85. package/bus/scripts/test-hub.ts +266 -0
  86. package/bus/scripts/test-local.sh +40 -0
  87. package/bus/scripts/test-queue.ts +132 -0
  88. package/bus/worker/package-lock.json +1971 -0
  89. package/bus/worker/package.json +17 -0
  90. package/bus/worker/src/index.ts +485 -0
  91. package/bus/worker/tsconfig.json +13 -0
  92. package/bus/worker/wrangler.toml +32 -0
  93. package/city/oven/README.md +59 -0
  94. package/city/oven/collect.py +43 -0
  95. package/city/oven/oven.html +4 -0
  96. package/city/scripts/history.py +120 -0
  97. package/city/scripts/seed.py +442 -0
  98. package/city/web/assets/modelos/License.txt +28 -0
  99. package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
  100. package/city/web/assets/modelos/building-a.glb +0 -0
  101. package/city/web/assets/modelos/building-b.glb +0 -0
  102. package/city/web/assets/modelos/building-c.glb +0 -0
  103. package/city/web/assets/modelos/building-d.glb +0 -0
  104. package/city/web/assets/modelos/building-e.glb +0 -0
  105. package/city/web/assets/modelos/building-f.glb +0 -0
  106. package/city/web/assets/modelos/building-g.glb +0 -0
  107. package/city/web/assets/modelos/building-h.glb +0 -0
  108. package/city/web/assets/sprites/building-a.png +0 -0
  109. package/city/web/assets/sprites/building-b.png +0 -0
  110. package/city/web/assets/sprites/building-c.png +0 -0
  111. package/city/web/assets/sprites/building-d.png +0 -0
  112. package/city/web/assets/sprites/building-e.png +0 -0
  113. package/city/web/assets/sprites/building-f.png +0 -0
  114. package/city/web/assets/sprites/building-g.png +0 -0
  115. package/city/web/assets/sprites/building-h.png +0 -0
  116. package/city/web/assets/sprites/building-i.png +0 -0
  117. package/city/web/assets/sprites/building-j.png +0 -0
  118. package/city/web/assets/sprites/building-k.png +0 -0
  119. package/city/web/assets/sprites/building-l.png +0 -0
  120. package/city/web/assets/sprites/building-m.png +0 -0
  121. package/city/web/assets/sprites/building-n.png +0 -0
  122. package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
  123. package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
  124. package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
  125. package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
  126. package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
  127. package/city/web/assets/sprites/catalogo.json +47 -0
  128. package/city/web/assets/sprites/detail-awning-wide.png +0 -0
  129. package/city/web/assets/sprites/detail-awning.png +0 -0
  130. package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
  131. package/city/web/assets/sprites/detail-overhang.png +0 -0
  132. package/city/web/assets/sprites/detail-parasol-a.png +0 -0
  133. package/city/web/assets/sprites/detail-parasol-b.png +0 -0
  134. package/city/web/assets/sprites/low-detail-building-a.png +0 -0
  135. package/city/web/assets/sprites/low-detail-building-b.png +0 -0
  136. package/city/web/assets/sprites/low-detail-building-c.png +0 -0
  137. package/city/web/assets/sprites/low-detail-building-d.png +0 -0
  138. package/city/web/assets/sprites/low-detail-building-e.png +0 -0
  139. package/city/web/assets/sprites/low-detail-building-f.png +0 -0
  140. package/city/web/assets/sprites/low-detail-building-g.png +0 -0
  141. package/city/web/assets/sprites/low-detail-building-h.png +0 -0
  142. package/city/web/assets/sprites/low-detail-building-i.png +0 -0
  143. package/city/web/assets/sprites/low-detail-building-j.png +0 -0
  144. package/city/web/assets/sprites/low-detail-building-k.png +0 -0
  145. package/city/web/assets/sprites/low-detail-building-l.png +0 -0
  146. package/city/web/assets/sprites/low-detail-building-m.png +0 -0
  147. package/city/web/assets/sprites/low-detail-building-n.png +0 -0
  148. package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
  149. package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
  150. package/city/web/assets/sprites/medidas.json +248 -0
  151. package/city/web/dist/city.js +939 -0
  152. package/city/web/dist/index.html +294 -0
  153. package/city/web/dist/modelos/License.txt +28 -0
  154. package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
  155. package/city/web/dist/modelos/building-a.glb +0 -0
  156. package/city/web/dist/modelos/building-b.glb +0 -0
  157. package/city/web/dist/modelos/building-c.glb +0 -0
  158. package/city/web/dist/modelos/building-d.glb +0 -0
  159. package/city/web/dist/modelos/building-e.glb +0 -0
  160. package/city/web/dist/modelos/building-f.glb +0 -0
  161. package/city/web/dist/modelos/building-g.glb +0 -0
  162. package/city/web/dist/modelos/building-h.glb +0 -0
  163. package/city/web/dist/sprites/building-a.png +0 -0
  164. package/city/web/dist/sprites/building-b.png +0 -0
  165. package/city/web/dist/sprites/building-c.png +0 -0
  166. package/city/web/dist/sprites/building-d.png +0 -0
  167. package/city/web/dist/sprites/building-e.png +0 -0
  168. package/city/web/dist/sprites/building-f.png +0 -0
  169. package/city/web/dist/sprites/building-g.png +0 -0
  170. package/city/web/dist/sprites/building-h.png +0 -0
  171. package/city/web/dist/sprites/building-i.png +0 -0
  172. package/city/web/dist/sprites/building-j.png +0 -0
  173. package/city/web/dist/sprites/building-k.png +0 -0
  174. package/city/web/dist/sprites/building-l.png +0 -0
  175. package/city/web/dist/sprites/building-m.png +0 -0
  176. package/city/web/dist/sprites/building-n.png +0 -0
  177. package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
  178. package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
  179. package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
  180. package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
  181. package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
  182. package/city/web/dist/sprites/catalogo.json +47 -0
  183. package/city/web/dist/sprites/detail-awning-wide.png +0 -0
  184. package/city/web/dist/sprites/detail-awning.png +0 -0
  185. package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
  186. package/city/web/dist/sprites/detail-overhang.png +0 -0
  187. package/city/web/dist/sprites/detail-parasol-a.png +0 -0
  188. package/city/web/dist/sprites/detail-parasol-b.png +0 -0
  189. package/city/web/dist/sprites/low-detail-building-a.png +0 -0
  190. package/city/web/dist/sprites/low-detail-building-b.png +0 -0
  191. package/city/web/dist/sprites/low-detail-building-c.png +0 -0
  192. package/city/web/dist/sprites/low-detail-building-d.png +0 -0
  193. package/city/web/dist/sprites/low-detail-building-e.png +0 -0
  194. package/city/web/dist/sprites/low-detail-building-f.png +0 -0
  195. package/city/web/dist/sprites/low-detail-building-g.png +0 -0
  196. package/city/web/dist/sprites/low-detail-building-h.png +0 -0
  197. package/city/web/dist/sprites/low-detail-building-i.png +0 -0
  198. package/city/web/dist/sprites/low-detail-building-j.png +0 -0
  199. package/city/web/dist/sprites/low-detail-building-k.png +0 -0
  200. package/city/web/dist/sprites/low-detail-building-l.png +0 -0
  201. package/city/web/dist/sprites/low-detail-building-m.png +0 -0
  202. package/city/web/dist/sprites/low-detail-building-n.png +0 -0
  203. package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
  204. package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
  205. package/city/web/dist/sprites/medidas.json +248 -0
  206. package/city/web/dist-hall/hall.js +1363 -0
  207. package/city/web/index.html +294 -0
  208. package/city/web/package.json +18 -0
  209. package/city/web/sella.py +21 -0
  210. package/city/web/src/activity-actors.ts +47 -0
  211. package/city/web/src/activity.ts +172 -0
  212. package/city/web/src/ayuntamiento.ts +580 -0
  213. package/city/web/src/draw.ts +640 -0
  214. package/city/web/src/game-speech.ts +79 -0
  215. package/city/web/src/hall.ts +1959 -0
  216. package/city/web/src/main.ts +2252 -0
  217. package/city/web/src/oven.ts +124 -0
  218. package/city/web/src/people.ts +244 -0
  219. package/city/web/src/presencia.ts +193 -0
  220. package/city/web/src/puertas.ts +180 -0
  221. package/city/web/src/repo-roles.ts +59 -0
  222. package/city/worker/package.json +16 -0
  223. package/city/worker/schema.sql +117 -0
  224. package/city/worker/src/index.ts +702 -0
  225. package/city/worker/src/square.ts +218 -0
  226. package/city/worker/wrangler.toml +58 -0
  227. package/demo/ada.md +50 -0
  228. package/demo/bruno.md +62 -0
  229. package/demo/camila.md +56 -0
  230. package/demo/city/ada.md +40 -0
  231. package/demo/city/city.yml +8 -0
  232. package/demo/city/parcels.yml +15 -0
  233. package/demo/city/roads.json +4 -0
  234. package/demo/city/units.yml +10 -0
  235. package/demo/clinica/city.yml +8 -0
  236. package/demo/clinica/parcels.yml +15 -0
  237. package/demo/clinica/roads.json +4 -0
  238. package/demo/clinica/units.yml +9 -0
  239. package/demo/clinica/vera.md +46 -0
  240. package/demo/dante.md +54 -0
  241. package/demo/despacho/city.yml +8 -0
  242. package/demo/despacho/marta.md +46 -0
  243. package/demo/despacho/parcels.yml +15 -0
  244. package/demo/despacho/roads.json +4 -0
  245. package/demo/despacho/units.yml +9 -0
  246. package/demo/elsa.md +50 -0
  247. package/demo/farid.md +52 -0
  248. package/demo/greta.md +50 -0
  249. package/demo/hugo.md +49 -0
  250. package/demo/iris.md +49 -0
  251. package/demo/jonas.md +51 -0
  252. package/demo/kira.md +51 -0
  253. package/demo/luca.md +49 -0
  254. package/demo/parcels.yml +92 -0
  255. package/demo/seed.py +20 -0
  256. package/demo/show.py +161 -0
  257. package/demo/stories.py +824 -0
  258. package/demo/units.yml +33 -0
  259. package/docs/agents-first.md +106 -0
  260. package/docs/glossary.md +55 -0
  261. package/docs/map-live-layers.md +73 -0
  262. package/docs/security.md +265 -0
  263. package/docs/self-host.md +151 -0
  264. package/docs/testing.md +205 -0
  265. package/package.json +73 -0
  266. package/plugin/.claude-plugin/plugin.json +62 -0
  267. package/plugin/.mcp.json +15 -0
  268. package/plugin/channel/activity-cli.ts +16 -0
  269. package/plugin/channel/adapter-prompts.ts +113 -0
  270. package/plugin/channel/adapter.js +4292 -0
  271. package/plugin/channel/adapter.ts +133 -0
  272. package/plugin/channel/bus.js +20188 -0
  273. package/plugin/channel/bus.ts +131 -0
  274. package/plugin/channel/city-config.ts +125 -0
  275. package/plugin/channel/claude-channel.ts +58 -0
  276. package/plugin/channel/cli-args.ts +75 -0
  277. package/plugin/channel/client.js +4200 -0
  278. package/plugin/channel/client.ts +38 -0
  279. package/plugin/channel/committee/activity.ts +256 -0
  280. package/plugin/channel/committee/collection.ts +154 -0
  281. package/plugin/channel/committee/decision.ts +175 -0
  282. package/plugin/channel/committee/floor.ts +135 -0
  283. package/plugin/channel/committee/guards.ts +30 -0
  284. package/plugin/channel/committee/history.ts +38 -0
  285. package/plugin/channel/committee/render.ts +102 -0
  286. package/plugin/channel/committee/service.ts +72 -0
  287. package/plugin/channel/committee/storage.ts +92 -0
  288. package/plugin/channel/committee/types.ts +163 -0
  289. package/plugin/channel/committee/view.ts +63 -0
  290. package/plugin/channel/committee-cli.ts +109 -0
  291. package/plugin/channel/delivery-metrics.ts +61 -0
  292. package/plugin/channel/delivery-queue.ts +118 -0
  293. package/plugin/channel/hub/activity-controller.ts +72 -0
  294. package/plugin/channel/hub/activity-feed.ts +169 -0
  295. package/plugin/channel/hub/committee-controller.ts +60 -0
  296. package/plugin/channel/hub/connections.ts +48 -0
  297. package/plugin/channel/hub/diagnostics.ts +67 -0
  298. package/plugin/channel/hub/envelope-validity.ts +109 -0
  299. package/plugin/channel/hub/envelopes.ts +66 -0
  300. package/plugin/channel/hub/lifecycle.ts +91 -0
  301. package/plugin/channel/hub/local-roads.ts +89 -0
  302. package/plugin/channel/hub/remote-roads.ts +165 -0
  303. package/plugin/channel/hub/road-controller.ts +99 -0
  304. package/plugin/channel/hub-client.ts +149 -0
  305. package/plugin/channel/local-hub.js +5995 -0
  306. package/plugin/channel/local-hub.ts +349 -0
  307. package/plugin/channel/map-reporter.ts +48 -0
  308. package/plugin/channel/package-lock.json +1731 -0
  309. package/plugin/channel/package.json +19 -0
  310. package/plugin/channel/protocol.ts +98 -0
  311. package/plugin/channel/road-cli.ts +18 -0
  312. package/plugin/channel/run.sh +15 -0
  313. package/plugin/channel/runtime/claude.ts +458 -0
  314. package/plugin/channel/runtime/codex-config.ts +77 -0
  315. package/plugin/channel/runtime/codex.ts +669 -0
  316. package/plugin/channel/runtime/command.ts +62 -0
  317. package/plugin/channel/runtime/factory.ts +15 -0
  318. package/plugin/channel/runtime/json-rpc.ts +154 -0
  319. package/plugin/channel/runtime/kimi.ts +277 -0
  320. package/plugin/channel/runtime/opencode.ts +248 -0
  321. package/plugin/channel/runtime/process.ts +84 -0
  322. package/plugin/channel/runtime/types.ts +45 -0
  323. package/plugin/channel/runtime-files.ts +73 -0
  324. package/plugin/channel/runtime-gateway.js +6396 -0
  325. package/plugin/channel/runtime-gateway.ts +358 -0
  326. package/plugin/channel/runtime-metrics.ts +52 -0
  327. package/plugin/channel/runtime-subscription.ts +180 -0
  328. package/plugin/channel/terminal-delivery.ts +131 -0
  329. package/plugin/channel/untrusted.ts +60 -0
  330. package/plugin/commands/committee.md +31 -0
  331. package/plugin/commands/exit.md +32 -0
  332. package/plugin/commands/goals.md +20 -0
  333. package/plugin/commands/join.md +18 -0
  334. package/plugin/commands/notice.md +20 -0
  335. package/plugin/commands/propose.md +19 -0
  336. package/plugin/commands/round.md +17 -0
  337. package/plugin/commands/session.md +22 -0
  338. package/plugin/commands/settings.md +25 -0
  339. package/plugin/commands/setup.md +21 -0
  340. package/plugin/commands/team.md +23 -0
  341. package/plugin/domains/custom.md +27 -0
  342. package/plugin/domains/finance.md +31 -0
  343. package/plugin/domains/healthcare.md +34 -0
  344. package/plugin/domains/legal.md +31 -0
  345. package/plugin/domains/marketing.md +33 -0
  346. package/plugin/domains/operations.md +31 -0
  347. package/plugin/domains/research.md +32 -0
  348. package/plugin/domains/sales.md +30 -0
  349. package/plugin/domains/software.md +37 -0
  350. package/plugin/hooks/activity.sh +6 -0
  351. package/plugin/hooks/digging.sh +68 -0
  352. package/plugin/hooks/growth.sh +42 -0
  353. package/plugin/hooks/hooks.json +106 -0
  354. package/plugin/hooks/notice-on-pr.sh +35 -0
  355. package/plugin/hooks/notice-on-stop.sh +80 -0
  356. package/plugin/hooks/notice-pending.sh +58 -0
  357. package/plugin/hooks/solo-en-ciudad.sh +23 -0
  358. package/plugin/hooks/tokens.sh +47 -0
  359. package/plugin/roles/examples/account-executive.md +25 -0
  360. package/plugin/roles/examples/ai-manager.md +34 -0
  361. package/plugin/roles/examples/associate.md +24 -0
  362. package/plugin/roles/examples/brand-lead.md +20 -0
  363. package/plugin/roles/examples/cfo.md +20 -0
  364. package/plugin/roles/examples/city-lead.md +25 -0
  365. package/plugin/roles/examples/clinical-director.md +26 -0
  366. package/plugin/roles/examples/clinical-ops.md +25 -0
  367. package/plugin/roles/examples/clinician.md +25 -0
  368. package/plugin/roles/examples/compliance.md +27 -0
  369. package/plugin/roles/examples/content.md +24 -0
  370. package/plugin/roles/examples/controller.md +27 -0
  371. package/plugin/roles/examples/cpto.md +41 -0
  372. package/plugin/roles/examples/customer-success.md +25 -0
  373. package/plugin/roles/examples/data-engineer.md +37 -0
  374. package/plugin/roles/examples/data.md +60 -0
  375. package/plugin/roles/examples/dev.md +37 -0
  376. package/plugin/roles/examples/devops.md +39 -0
  377. package/plugin/roles/examples/enablement.md +24 -0
  378. package/plugin/roles/examples/ethics.md +24 -0
  379. package/plugin/roles/examples/fin-analytics.md +27 -0
  380. package/plugin/roles/examples/health-compliance.md +25 -0
  381. package/plugin/roles/examples/health-data.md +26 -0
  382. package/plugin/roles/examples/knowledge.md +25 -0
  383. package/plugin/roles/examples/lifecycle.md +26 -0
  384. package/plugin/roles/examples/llm-engineer.md +34 -0
  385. package/plugin/roles/examples/managing-partner.md +20 -0
  386. package/plugin/roles/examples/methods.md +23 -0
  387. package/plugin/roles/examples/operations-lead.md +23 -0
  388. package/plugin/roles/examples/ops.md +24 -0
  389. package/plugin/roles/examples/patient-safety.md +26 -0
  390. package/plugin/roles/examples/performance.md +27 -0
  391. package/plugin/roles/examples/po.md +36 -0
  392. package/plugin/roles/examples/process-owner.md +23 -0
  393. package/plugin/roles/examples/product-design.md +39 -0
  394. package/plugin/roles/examples/program-manager.md +24 -0
  395. package/plugin/roles/examples/quality.md +25 -0
  396. package/plugin/roles/examples/research-director.md +24 -0
  397. package/plugin/roles/examples/research-ops.md +25 -0
  398. package/plugin/roles/examples/researcher.md +25 -0
  399. package/plugin/roles/examples/revenue-lead.md +25 -0
  400. package/plugin/roles/examples/revops.md +26 -0
  401. package/plugin/roles/examples/seo.md +27 -0
  402. package/plugin/roles/examples/specialist.md +24 -0
  403. package/plugin/scripts/actualiza.py +198 -0
  404. package/plugin/scripts/admision.py +165 -0
  405. package/plugin/scripts/apaga.py +232 -0
  406. package/plugin/scripts/atajos.py +506 -0
  407. package/plugin/scripts/avatar.py +211 -0
  408. package/plugin/scripts/broker.py +531 -0
  409. package/plugin/scripts/cage.py +492 -0
  410. package/plugin/scripts/capabilities.py +210 -0
  411. package/plugin/scripts/card.py +322 -0
  412. package/plugin/scripts/cities.py +520 -0
  413. package/plugin/scripts/city-env.sh +90 -0
  414. package/plugin/scripts/city-runtime.sh +87 -0
  415. package/plugin/scripts/city-session.sh +552 -0
  416. package/plugin/scripts/city_env.py +67 -0
  417. package/plugin/scripts/crecimiento.py +140 -0
  418. package/plugin/scripts/deliberations.py +48 -0
  419. package/plugin/scripts/doctor.py +294 -0
  420. package/plugin/scripts/domains.py +201 -0
  421. package/plugin/scripts/evidencia.py +64 -0
  422. package/plugin/scripts/find-repos.sh +111 -0
  423. package/plugin/scripts/gh.py +159 -0
  424. package/plugin/scripts/hall_protocol.py +147 -0
  425. package/plugin/scripts/hook_activity.py +139 -0
  426. package/plugin/scripts/launch.py +94 -0
  427. package/plugin/scripts/logs.py +98 -0
  428. package/plugin/scripts/pairing.py +185 -0
  429. package/plugin/scripts/parcels.py +132 -0
  430. package/plugin/scripts/read-card.py +55 -0
  431. package/plugin/scripts/report.py +201 -0
  432. package/plugin/scripts/reset.py +180 -0
  433. package/plugin/scripts/roads.py +230 -0
  434. package/plugin/scripts/roles.py +274 -0
  435. package/plugin/scripts/runtime_log.py +69 -0
  436. package/plugin/scripts/runtime_processes.py +132 -0
  437. package/plugin/scripts/rutas.py +83 -0
  438. package/plugin/scripts/seat.py +1696 -0
  439. package/plugin/scripts/tokens.py +194 -0
  440. package/plugin/scripts/trust-repos.py +59 -0
  441. package/plugin/scripts/ui.py +280 -0
  442. package/plugin/scripts/units.py +80 -0
  443. package/plugin/scripts/workspace.py +561 -0
  444. package/plugin/skills/city/SKILL.md +191 -0
  445. package/templates/blank.md +39 -0
  446. package/templates/finance.md +44 -0
  447. package/templates/legal.md +45 -0
  448. package/templates/marketing.md +48 -0
  449. package/templates/product.md +45 -0
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env python3
2
+ """The broker: real git against a bare origin, a stubbed gh, a live server.
3
+
4
+ Everything runs below a throwaway HOME. The happy paths use a REAL `git push`
5
+ to a local bare remote — no network — and a `gh` stub planted on PATH that
6
+ records its arguments. The unhappy paths are the product: work on the default
7
+ branch refused, a missing token 401, a wrong token 403, a tampered audit line
8
+ caught by the chain, and a stopped broker that leaves no orphan behind.
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ import tempfile
17
+ import time
18
+ import urllib.error
19
+ import urllib.request
20
+
21
+ AQUI = os.path.dirname(os.path.abspath(__file__))
22
+ RAIZ = os.path.dirname(AQUI)
23
+ sys.path.insert(0, os.path.join(RAIZ, "plugin", "scripts"))
24
+ sys.path.insert(0, AQUI)
25
+
26
+ import broker # noqa: E402
27
+ from testlib import afirma, comprueba, detiene_proceso, resumen # noqa: E402
28
+
29
+ BROKER = os.path.join(RAIZ, "plugin", "scripts", "broker.py")
30
+
31
+
32
+ def _git(repo, *args):
33
+ return subprocess.run(["git", "-C", repo, *args], capture_output=True, text=True)
34
+
35
+
36
+ def prepara():
37
+ base = tempfile.mkdtemp(prefix="agents-city-broker-")
38
+ os.environ["AGENTS_CITY_HOME"] = os.path.join(base, "app")
39
+ datos = os.path.join(base, "city")
40
+ os.makedirs(datos)
41
+ origen = os.path.join(base, "origin.git")
42
+ subprocess.run(["git", "init", "-q", "--bare", origen], check=True)
43
+ repo = os.path.join(base, "repo")
44
+ subprocess.run(["git", "init", "-q", "-b", "main", repo], check=True)
45
+ _git(repo, "config", "user.email", "t@t")
46
+ _git(repo, "config", "user.name", "t")
47
+ with open(os.path.join(repo, "f.txt"), "w") as f:
48
+ f.write("a\n")
49
+ _git(repo, "add", "f.txt")
50
+ _git(repo, "commit", "-qm", "first")
51
+ _git(repo, "remote", "add", "origin", origen)
52
+ _git(repo, "push", "-q", "origin", "main")
53
+ stubs = os.path.join(base, "stubs")
54
+ os.makedirs(stubs)
55
+ with open(os.path.join(stubs, "gh"), "w") as f:
56
+ f.write('#!/bin/sh\necho "$@" > "$GH_STUB_LOG"\n'
57
+ 'echo "https://github.com/x/y/pull/7"\n')
58
+ os.chmod(os.path.join(stubs, "gh"), 0o755)
59
+ return base, datos, repo, origen, stubs
60
+
61
+
62
+ def pide(url, ruta, token, cuerpo=None):
63
+ peticion = urllib.request.Request(
64
+ url + ruta, data=json.dumps(cuerpo or {}).encode(), method="POST",
65
+ headers={"Authorization": f"Bearer {token}"} if token else {})
66
+ try:
67
+ with urllib.request.urlopen(peticion, timeout=10) as r:
68
+ return r.status, json.loads(r.read())
69
+ except urllib.error.HTTPError as e:
70
+ return e.code, json.loads(e.read() or b"{}")
71
+
72
+
73
+ def arranca(datos, entorno):
74
+ proceso = subprocess.Popen([sys.executable, BROKER, "serve", "--data", datos],
75
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
76
+ text=True, env=entorno)
77
+ limite = time.monotonic() + 10
78
+ while time.monotonic() < limite:
79
+ url = broker.url_de(datos)
80
+ if url:
81
+ return proceso, url
82
+ time.sleep(0.05)
83
+ proceso.kill()
84
+ # Never a blind failure: whatever the child said is the whole diagnosis,
85
+ # and CI is the one place nobody can re-run it by hand to find out.
86
+ salida, error = proceso.communicate(timeout=5)
87
+ raise RuntimeError(
88
+ f"the broker never wrote its endpoint\n stdout: {salida.strip()}\n"
89
+ f" stderr: {error.strip()}"
90
+ )
91
+
92
+
93
+ def todo():
94
+ base, datos, repo, origen, stubs = prepara()
95
+ entorno = dict(os.environ, PATH=stubs + os.pathsep + os.environ["PATH"],
96
+ GH_STUB_LOG=os.path.join(base, "gh.log"))
97
+ proceso = None
98
+ try:
99
+ ficha = broker.acuna(datos, "web", repo, solo_fichero=True)
100
+ afirma("mint writes the token file with 0600",
101
+ oct(os.stat(ficha).st_mode & 0o777) == "0o600", ficha)
102
+ token = open(ficha).read().strip()
103
+ guardados = json.load(open(os.path.join(broker.estado_de(datos), "tokens.json")))
104
+ afirma("only the hash of the token is stored, never the token",
105
+ token not in json.dumps(guardados) and len(guardados) == 1)
106
+ try:
107
+ broker.acuna(datos, "x", os.path.join(base, "not-a-repo"))
108
+ afirma("minting against a non-repo is refused", False)
109
+ except ValueError:
110
+ afirma("minting against a non-repo is refused", True)
111
+
112
+ proceso, url = arranca(datos, entorno)
113
+
114
+ estado, _ = pide(url, "/v1/push", token)
115
+ comprueba("push on the default branch is refused with 422", estado, 422)
116
+ estado, _ = pide(url, "/v1/pr", token, {"title": "x"})
117
+ comprueba("a PR from the default branch is refused too", estado, 422)
118
+
119
+ _git(repo, "checkout", "-qb", "feature")
120
+ with open(os.path.join(repo, "f.txt"), "w") as f:
121
+ f.write("b\n")
122
+ _git(repo, "commit", "-qam", "change")
123
+
124
+ estado, cuerpo = pide(url, "/v1/push", token)
125
+ comprueba("push on a feature branch succeeds", estado, 200)
126
+ afirma("the bare origin actually received the branch",
127
+ _git(origen, "rev-parse", "feature").returncode == 0)
128
+ comprueba("the response names the branch it pushed", cuerpo.get("branch"), "feature")
129
+
130
+ estado, cuerpo = pide(url, "/v1/pr", token, {"title": "Add b", "body": "why"})
131
+ comprueba("gh runs and the PR URL comes back", estado, 200)
132
+ comprueba("the URL is gh's answer, verbatim",
133
+ cuerpo.get("url"), "https://github.com/x/y/pull/7")
134
+ registrado = open(os.path.join(base, "gh.log")).read()
135
+ afirma("gh received the title the window asked for", "Add b" in registrado, registrado)
136
+
137
+ estado, _ = pide(url, "/v1/pr", token, {"title": ""})
138
+ comprueba("a PR without a title is refused", estado, 422)
139
+ estado, _ = pide(url, "/v1/push", "")
140
+ comprueba("no token at all is a 401", estado, 401)
141
+ estado, _ = pide(url, "/v1/push", "cb_" + "0" * 48)
142
+ comprueba("a wrong token is a 403", estado, 403)
143
+ estado, _ = pide(url, "/v1/nothing", token)
144
+ comprueba("an unknown verb is a 404", estado, 404)
145
+
146
+ registro = os.path.join(broker.estado_de(datos), "audit.log")
147
+ lineas = open(registro).read().splitlines()
148
+ afirma("every request, refused ones included, landed in the audit log",
149
+ sum(1 for l in lineas if '"/v1/' in l) >= 7, f"{len(lineas)} lines")
150
+ comprueba("the audit chain verifies intact", broker.verifica(broker.estado_de(datos)), 0)
151
+ manipuladas = list(lineas)
152
+ falseada = json.loads(manipuladas[2])
153
+ falseada["ok"] = not falseada["ok"]
154
+ manipuladas[2] = json.dumps(falseada, sort_keys=True)
155
+ with open(registro, "w") as f:
156
+ f.write("\n".join(manipuladas) + "\n")
157
+ afirma("one rewritten entry breaks the chain at the next line",
158
+ broker.verifica(broker.estado_de(datos)) == 4,
159
+ f"broken at {broker.verifica(broker.estado_de(datos))}")
160
+
161
+ afirma("stop finds the pid it published and signals it", broker.para(datos))
162
+ try:
163
+ proceso.wait(timeout=5)
164
+ afirma("the broker is gone, no orphan left", detiene_proceso(proceso.pid))
165
+ except subprocess.TimeoutExpired:
166
+ afirma("the broker is gone, no orphan left", False)
167
+ proceso = None
168
+ comprueba("a stopped broker publishes no url", broker.url_de(datos), "")
169
+ finally:
170
+ if proceso:
171
+ proceso.kill()
172
+ shutil.rmtree(base, ignore_errors=True)
173
+ os.environ.pop("AGENTS_CITY_HOME", None)
174
+
175
+
176
+ def secretos_y_evidencia():
177
+ from evidencia import Evidencia
178
+
179
+ base = tempfile.mkdtemp(prefix="agents-city-broker-")
180
+ os.environ["AGENTS_CITY_HOME"] = os.path.join(base, "app")
181
+ try:
182
+ datos = os.path.join(base, "city")
183
+ os.makedirs(datos)
184
+ estado = broker.estado_de(datos)
185
+
186
+ # Binding rules: a host is required, and wildcards/ports are refused.
187
+ for malo in ("*.example.com", "api.example.com:443", "a/b"):
188
+ try:
189
+ broker.guarda_secreto(datos, "K", "v", [malo])
190
+ afirma(f"host '{malo}' is refused", False)
191
+ except ValueError:
192
+ afirma(f"host '{malo}' is refused", True)
193
+ try:
194
+ broker.guarda_secreto(datos, "K", "v", [])
195
+ afirma("a secret with no host is refused", False)
196
+ except ValueError:
197
+ afirma("a secret with no host is refused", True)
198
+
199
+ broker.guarda_secreto(datos, "OPENAI", "sk-REAL-SECRET", ["api.openai.com"])
200
+ valor, motivo = broker._host_permitido(estado, "OPENAI", "api.openai.com")
201
+ comprueba("a bound host resolves the value", valor, "sk-REAL-SECRET")
202
+ _, motivo = broker._host_permitido(estado, "OPENAI", "evil.example.com")
203
+ afirma("an unbound host is refused with a reason", motivo is not None)
204
+ _, motivo = broker._host_permitido(estado, "NOPE", "api.openai.com")
205
+ afirma("an unknown secret name is refused", motivo is not None)
206
+
207
+ quien = {"actor": "web", "repo": base}
208
+ code, resp = broker.accion_fetch(quien, {"secret": "OPENAI"}, estado)
209
+ comprueba("fetch without a host is 422", code, 422)
210
+ code, resp = broker.accion_fetch(
211
+ quien, {"secret": "OPENAI", "host": "evil.example.com", "path": "/v1/x"}, estado)
212
+ comprueba("fetch to an unbound host is 403", code, 403)
213
+ afirma("the 403 never echoes the secret value", "sk-REAL-SECRET" not in str(resp))
214
+ code, resp = broker.accion_fetch(
215
+ quien, {"secret": "OPENAI", "host": "api.openai.com", "method": "DELETE"}, estado)
216
+ comprueba("an unsupported method is 422", code, 422)
217
+
218
+ # The success path: capture the request without a socket, prove the
219
+ # secret is injected into the declared header only for the bound host.
220
+ capturado = {}
221
+
222
+ class _Resp:
223
+ status = 200
224
+
225
+ def __enter__(self):
226
+ return self
227
+
228
+ def __exit__(self, *a):
229
+ return False
230
+
231
+ def read(self, *_):
232
+ return b'{"ok":true}'
233
+
234
+ def _fake_open(req, timeout=0):
235
+ capturado["url"] = req.full_url
236
+ capturado["auth"] = req.headers.get("X-api-key") or req.headers.get("Authorization")
237
+ return _Resp()
238
+
239
+ # Patch the no-redirect opener the action actually uses.
240
+ original = broker._SIN_REDIRECCIONES.open
241
+ broker._SIN_REDIRECCIONES.open = _fake_open
242
+ try:
243
+ code, resp = broker.accion_fetch(
244
+ quien,
245
+ {"secret": "OPENAI", "host": "api.openai.com", "path": "/v1/models",
246
+ "header": "X-Api-Key", "template": "{secret}"},
247
+ estado)
248
+ # Control characters in a window-controlled field are refused as 422,
249
+ # not left to raise deep inside urllib as a 500.
250
+ code_ctl, _ = broker.accion_fetch(
251
+ quien,
252
+ {"secret": "OPENAI", "host": "api.openai.com", "path": "/x",
253
+ "header": "X-Api-Key", "template": "{secret}\r\nX-Injected: 1"},
254
+ estado)
255
+ finally:
256
+ broker._SIN_REDIRECCIONES.open = original
257
+ comprueba("a bound fetch returns 200", code, 200)
258
+ comprueba("it hit the exact bound host over https",
259
+ capturado["url"], "https://api.openai.com/v1/models")
260
+ comprueba("the secret was injected into the declared header",
261
+ capturado["auth"], "sk-REAL-SECRET")
262
+ afirma("and the broker's own response never carries the secret",
263
+ "sk-REAL-SECRET" not in str(resp))
264
+ comprueba("a CRLF-injected header is refused with 422", code_ctl, 422)
265
+ afirma("the redirect handler refuses to follow any 3xx (secret stays on host)",
266
+ broker._NoSigue().redirect_request(None, None, None, None, None) is None)
267
+
268
+ # Every audit line carries an evidence state, and the chain holds.
269
+ lineas = open(os.path.join(estado, "audit.log")).read().splitlines()
270
+ estados = {json.loads(x).get("evidence") for x in lineas}
271
+ afirma("audit lines carry evidence states",
272
+ str(Evidencia.IMPUESTO) in estados)
273
+ afirma("no audit line ever claims 'unknown means ok'",
274
+ all(not (json.loads(x).get("ok") and json.loads(x).get("evidence") ==
275
+ str(Evidencia.DESCONOCIDO)) for x in lineas))
276
+ comprueba("the chain verifies intact after secret ops",
277
+ broker.verifica(estado), 0)
278
+ finally:
279
+ shutil.rmtree(base, ignore_errors=True)
280
+ os.environ.pop("AGENTS_CITY_HOME", None)
281
+
282
+
283
+ def arranca_sin_dns_inversa():
284
+ """A loopback broker must start without waiting on the network.
285
+
286
+ The stdlib's HTTPServer.server_bind calls socket.getfqdn() for a cosmetic
287
+ name — a reverse DNS lookup. On a resolver that never answers (a CI macOS
288
+ runner, an airplane, a locked-down office LAN) it blocks for tens of
289
+ seconds, and the broker's endpoint appears only after it returns. This is
290
+ that hang's tombstone: getfqdn is sabotaged to take forever, and the
291
+ broker must still be serving.
292
+ """
293
+ import socket
294
+ import threading
295
+
296
+ base = tempfile.mkdtemp(prefix="agents-city-broker-dns-")
297
+ os.environ["AGENTS_CITY_HOME"] = os.path.join(base, "app")
298
+ datos = os.path.join(base, "city")
299
+ os.makedirs(datos)
300
+ original = socket.getfqdn
301
+ socket.getfqdn = lambda *a: time.sleep(60) or "nunca"
302
+ servidor = None
303
+ try:
304
+ arrancado = threading.Event()
305
+
306
+ def sirve_en_hilo():
307
+ nonlocal servidor
308
+ servidor = broker.Servidor(("127.0.0.1", 0), broker.Manejador)
309
+ arrancado.set()
310
+ servidor.serve_forever()
311
+
312
+ broker.Manejador.estado = broker.estado_de(datos)
313
+ hilo = threading.Thread(target=sirve_en_hilo, daemon=True)
314
+ comienzo = time.monotonic()
315
+ hilo.start()
316
+ listo = arrancado.wait(timeout=5)
317
+ afirma(
318
+ "the broker binds without waiting on reverse DNS",
319
+ listo and time.monotonic() - comienzo < 5,
320
+ f"{round(time.monotonic() - comienzo, 2)}s",
321
+ )
322
+ finally:
323
+ socket.getfqdn = original
324
+ if servidor is not None:
325
+ servidor.shutdown()
326
+ servidor.server_close()
327
+ shutil.rmtree(base, ignore_errors=True)
328
+ os.environ.pop("AGENTS_CITY_HOME", None)
329
+
330
+
331
+ todo()
332
+ secretos_y_evidencia()
333
+ arranca_sin_dns_inversa()
334
+ sys.exit(resumen("broker"))