agents-city 0.3.0-beta.21

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 (440) 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 +2021 -0
  5. package/README.md +1999 -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 +116 -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/exit +5 -0
  28. package/bin/hall +73 -0
  29. package/bin/hall.html +426 -0
  30. package/bin/logs +3 -0
  31. package/bin/report.py +14 -0
  32. package/bin/reset +3 -0
  33. package/bin/road +3 -0
  34. package/bin/seat +10 -0
  35. package/bin/serve.py +1428 -0
  36. package/bin/setup.py +753 -0
  37. package/bin/skills +3 -0
  38. package/bin/test +131 -0
  39. package/bin/test-adapter.py +183 -0
  40. package/bin/test-admision.py +99 -0
  41. package/bin/test-avatar.py +58 -0
  42. package/bin/test-benchmark.py +40 -0
  43. package/bin/test-broker.py +334 -0
  44. package/bin/test-cage.py +206 -0
  45. package/bin/test-card.py +402 -0
  46. package/bin/test-channel.py +379 -0
  47. package/bin/test-cities.py +453 -0
  48. package/bin/test-claude-runtime.py +252 -0
  49. package/bin/test-committee.py +340 -0
  50. package/bin/test-contracts.py +544 -0
  51. package/bin/test-crecimiento.py +164 -0
  52. package/bin/test-demo.py +308 -0
  53. package/bin/test-doctor.py +106 -0
  54. package/bin/test-domains.py +151 -0
  55. package/bin/test-evidencia.py +50 -0
  56. package/bin/test-exit.py +128 -0
  57. package/bin/test-hall-protocol.py +93 -0
  58. package/bin/test-launch.py +169 -0
  59. package/bin/test-live-feed.py +508 -0
  60. package/bin/test-pairing.py +99 -0
  61. package/bin/test-parcels.py +283 -0
  62. package/bin/test-runtime-failures.py +252 -0
  63. package/bin/test-runtime-ui.py +488 -0
  64. package/bin/test-runtime.py +268 -0
  65. package/bin/test-rutas.py +80 -0
  66. package/bin/test-seat.py +1386 -0
  67. package/bin/test-security.py +150 -0
  68. package/bin/test-serve.py +1057 -0
  69. package/bin/test-stress.py +64 -0
  70. package/bin/test-widgets.py +143 -0
  71. package/bin/test-workspace.py +188 -0
  72. package/bin/testlib.py +95 -0
  73. package/bin/tokens.py +14 -0
  74. package/bus/scripts/deploy.sh +60 -0
  75. package/bus/scripts/mint-token.sh +68 -0
  76. package/bus/scripts/setup-dev.sh +79 -0
  77. package/bus/scripts/test-channel.ts +8 -0
  78. package/bus/scripts/test-hub.ts +266 -0
  79. package/bus/scripts/test-local.sh +40 -0
  80. package/bus/scripts/test-queue.ts +132 -0
  81. package/bus/worker/package-lock.json +1971 -0
  82. package/bus/worker/package.json +17 -0
  83. package/bus/worker/src/index.ts +485 -0
  84. package/bus/worker/tsconfig.json +13 -0
  85. package/bus/worker/wrangler.toml +32 -0
  86. package/city/oven/README.md +59 -0
  87. package/city/oven/collect.py +43 -0
  88. package/city/oven/oven.html +4 -0
  89. package/city/scripts/history.py +120 -0
  90. package/city/scripts/seed.py +442 -0
  91. package/city/web/assets/modelos/License.txt +28 -0
  92. package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
  93. package/city/web/assets/modelos/building-a.glb +0 -0
  94. package/city/web/assets/modelos/building-b.glb +0 -0
  95. package/city/web/assets/modelos/building-c.glb +0 -0
  96. package/city/web/assets/modelos/building-d.glb +0 -0
  97. package/city/web/assets/modelos/building-e.glb +0 -0
  98. package/city/web/assets/modelos/building-f.glb +0 -0
  99. package/city/web/assets/modelos/building-g.glb +0 -0
  100. package/city/web/assets/modelos/building-h.glb +0 -0
  101. package/city/web/assets/sprites/building-a.png +0 -0
  102. package/city/web/assets/sprites/building-b.png +0 -0
  103. package/city/web/assets/sprites/building-c.png +0 -0
  104. package/city/web/assets/sprites/building-d.png +0 -0
  105. package/city/web/assets/sprites/building-e.png +0 -0
  106. package/city/web/assets/sprites/building-f.png +0 -0
  107. package/city/web/assets/sprites/building-g.png +0 -0
  108. package/city/web/assets/sprites/building-h.png +0 -0
  109. package/city/web/assets/sprites/building-i.png +0 -0
  110. package/city/web/assets/sprites/building-j.png +0 -0
  111. package/city/web/assets/sprites/building-k.png +0 -0
  112. package/city/web/assets/sprites/building-l.png +0 -0
  113. package/city/web/assets/sprites/building-m.png +0 -0
  114. package/city/web/assets/sprites/building-n.png +0 -0
  115. package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
  116. package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
  117. package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
  118. package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
  119. package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
  120. package/city/web/assets/sprites/catalogo.json +47 -0
  121. package/city/web/assets/sprites/detail-awning-wide.png +0 -0
  122. package/city/web/assets/sprites/detail-awning.png +0 -0
  123. package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
  124. package/city/web/assets/sprites/detail-overhang.png +0 -0
  125. package/city/web/assets/sprites/detail-parasol-a.png +0 -0
  126. package/city/web/assets/sprites/detail-parasol-b.png +0 -0
  127. package/city/web/assets/sprites/low-detail-building-a.png +0 -0
  128. package/city/web/assets/sprites/low-detail-building-b.png +0 -0
  129. package/city/web/assets/sprites/low-detail-building-c.png +0 -0
  130. package/city/web/assets/sprites/low-detail-building-d.png +0 -0
  131. package/city/web/assets/sprites/low-detail-building-e.png +0 -0
  132. package/city/web/assets/sprites/low-detail-building-f.png +0 -0
  133. package/city/web/assets/sprites/low-detail-building-g.png +0 -0
  134. package/city/web/assets/sprites/low-detail-building-h.png +0 -0
  135. package/city/web/assets/sprites/low-detail-building-i.png +0 -0
  136. package/city/web/assets/sprites/low-detail-building-j.png +0 -0
  137. package/city/web/assets/sprites/low-detail-building-k.png +0 -0
  138. package/city/web/assets/sprites/low-detail-building-l.png +0 -0
  139. package/city/web/assets/sprites/low-detail-building-m.png +0 -0
  140. package/city/web/assets/sprites/low-detail-building-n.png +0 -0
  141. package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
  142. package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
  143. package/city/web/assets/sprites/medidas.json +248 -0
  144. package/city/web/dist/city.js +939 -0
  145. package/city/web/dist/index.html +294 -0
  146. package/city/web/dist/modelos/License.txt +28 -0
  147. package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
  148. package/city/web/dist/modelos/building-a.glb +0 -0
  149. package/city/web/dist/modelos/building-b.glb +0 -0
  150. package/city/web/dist/modelos/building-c.glb +0 -0
  151. package/city/web/dist/modelos/building-d.glb +0 -0
  152. package/city/web/dist/modelos/building-e.glb +0 -0
  153. package/city/web/dist/modelos/building-f.glb +0 -0
  154. package/city/web/dist/modelos/building-g.glb +0 -0
  155. package/city/web/dist/modelos/building-h.glb +0 -0
  156. package/city/web/dist/sprites/building-a.png +0 -0
  157. package/city/web/dist/sprites/building-b.png +0 -0
  158. package/city/web/dist/sprites/building-c.png +0 -0
  159. package/city/web/dist/sprites/building-d.png +0 -0
  160. package/city/web/dist/sprites/building-e.png +0 -0
  161. package/city/web/dist/sprites/building-f.png +0 -0
  162. package/city/web/dist/sprites/building-g.png +0 -0
  163. package/city/web/dist/sprites/building-h.png +0 -0
  164. package/city/web/dist/sprites/building-i.png +0 -0
  165. package/city/web/dist/sprites/building-j.png +0 -0
  166. package/city/web/dist/sprites/building-k.png +0 -0
  167. package/city/web/dist/sprites/building-l.png +0 -0
  168. package/city/web/dist/sprites/building-m.png +0 -0
  169. package/city/web/dist/sprites/building-n.png +0 -0
  170. package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
  171. package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
  172. package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
  173. package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
  174. package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
  175. package/city/web/dist/sprites/catalogo.json +47 -0
  176. package/city/web/dist/sprites/detail-awning-wide.png +0 -0
  177. package/city/web/dist/sprites/detail-awning.png +0 -0
  178. package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
  179. package/city/web/dist/sprites/detail-overhang.png +0 -0
  180. package/city/web/dist/sprites/detail-parasol-a.png +0 -0
  181. package/city/web/dist/sprites/detail-parasol-b.png +0 -0
  182. package/city/web/dist/sprites/low-detail-building-a.png +0 -0
  183. package/city/web/dist/sprites/low-detail-building-b.png +0 -0
  184. package/city/web/dist/sprites/low-detail-building-c.png +0 -0
  185. package/city/web/dist/sprites/low-detail-building-d.png +0 -0
  186. package/city/web/dist/sprites/low-detail-building-e.png +0 -0
  187. package/city/web/dist/sprites/low-detail-building-f.png +0 -0
  188. package/city/web/dist/sprites/low-detail-building-g.png +0 -0
  189. package/city/web/dist/sprites/low-detail-building-h.png +0 -0
  190. package/city/web/dist/sprites/low-detail-building-i.png +0 -0
  191. package/city/web/dist/sprites/low-detail-building-j.png +0 -0
  192. package/city/web/dist/sprites/low-detail-building-k.png +0 -0
  193. package/city/web/dist/sprites/low-detail-building-l.png +0 -0
  194. package/city/web/dist/sprites/low-detail-building-m.png +0 -0
  195. package/city/web/dist/sprites/low-detail-building-n.png +0 -0
  196. package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
  197. package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
  198. package/city/web/dist/sprites/medidas.json +248 -0
  199. package/city/web/dist-hall/hall.js +264 -0
  200. package/city/web/index.html +294 -0
  201. package/city/web/package.json +18 -0
  202. package/city/web/sella.py +21 -0
  203. package/city/web/src/activity-actors.ts +47 -0
  204. package/city/web/src/activity.ts +172 -0
  205. package/city/web/src/ayuntamiento.ts +580 -0
  206. package/city/web/src/draw.ts +640 -0
  207. package/city/web/src/game-speech.ts +79 -0
  208. package/city/web/src/hall.ts +1867 -0
  209. package/city/web/src/main.ts +2225 -0
  210. package/city/web/src/oven.ts +124 -0
  211. package/city/web/src/people.ts +244 -0
  212. package/city/web/src/presencia.ts +193 -0
  213. package/city/web/src/puertas.ts +180 -0
  214. package/city/web/src/repo-roles.ts +59 -0
  215. package/city/worker/package.json +16 -0
  216. package/city/worker/schema.sql +117 -0
  217. package/city/worker/src/index.ts +702 -0
  218. package/city/worker/src/square.ts +218 -0
  219. package/city/worker/wrangler.toml +58 -0
  220. package/demo/ada.md +50 -0
  221. package/demo/bruno.md +62 -0
  222. package/demo/camila.md +56 -0
  223. package/demo/city/ada.md +40 -0
  224. package/demo/city/city.yml +8 -0
  225. package/demo/city/parcels.yml +15 -0
  226. package/demo/city/roads.json +4 -0
  227. package/demo/city/units.yml +10 -0
  228. package/demo/clinica/city.yml +8 -0
  229. package/demo/clinica/parcels.yml +15 -0
  230. package/demo/clinica/roads.json +4 -0
  231. package/demo/clinica/units.yml +9 -0
  232. package/demo/clinica/vera.md +46 -0
  233. package/demo/dante.md +54 -0
  234. package/demo/despacho/city.yml +8 -0
  235. package/demo/despacho/marta.md +46 -0
  236. package/demo/despacho/parcels.yml +15 -0
  237. package/demo/despacho/roads.json +4 -0
  238. package/demo/despacho/units.yml +9 -0
  239. package/demo/elsa.md +50 -0
  240. package/demo/farid.md +52 -0
  241. package/demo/greta.md +50 -0
  242. package/demo/hugo.md +49 -0
  243. package/demo/iris.md +49 -0
  244. package/demo/jonas.md +51 -0
  245. package/demo/kira.md +51 -0
  246. package/demo/luca.md +49 -0
  247. package/demo/parcels.yml +92 -0
  248. package/demo/seed.py +20 -0
  249. package/demo/show.py +161 -0
  250. package/demo/stories.py +824 -0
  251. package/demo/units.yml +33 -0
  252. package/docs/agents-first.md +99 -0
  253. package/docs/glossary.md +55 -0
  254. package/docs/map-live-layers.md +73 -0
  255. package/docs/security.md +231 -0
  256. package/docs/self-host.md +151 -0
  257. package/docs/testing.md +205 -0
  258. package/package.json +73 -0
  259. package/plugin/.claude-plugin/plugin.json +62 -0
  260. package/plugin/.mcp.json +15 -0
  261. package/plugin/channel/activity-cli.ts +16 -0
  262. package/plugin/channel/adapter-prompts.ts +113 -0
  263. package/plugin/channel/adapter.js +4292 -0
  264. package/plugin/channel/adapter.ts +133 -0
  265. package/plugin/channel/bus.js +20188 -0
  266. package/plugin/channel/bus.ts +131 -0
  267. package/plugin/channel/city-config.ts +125 -0
  268. package/plugin/channel/claude-channel.ts +58 -0
  269. package/plugin/channel/cli-args.ts +75 -0
  270. package/plugin/channel/client.js +4200 -0
  271. package/plugin/channel/client.ts +38 -0
  272. package/plugin/channel/committee/activity.ts +256 -0
  273. package/plugin/channel/committee/collection.ts +154 -0
  274. package/plugin/channel/committee/decision.ts +175 -0
  275. package/plugin/channel/committee/floor.ts +135 -0
  276. package/plugin/channel/committee/guards.ts +30 -0
  277. package/plugin/channel/committee/history.ts +38 -0
  278. package/plugin/channel/committee/render.ts +102 -0
  279. package/plugin/channel/committee/service.ts +72 -0
  280. package/plugin/channel/committee/storage.ts +92 -0
  281. package/plugin/channel/committee/types.ts +163 -0
  282. package/plugin/channel/committee/view.ts +63 -0
  283. package/plugin/channel/committee-cli.ts +109 -0
  284. package/plugin/channel/delivery-metrics.ts +61 -0
  285. package/plugin/channel/delivery-queue.ts +118 -0
  286. package/plugin/channel/hub/activity-controller.ts +72 -0
  287. package/plugin/channel/hub/activity-feed.ts +169 -0
  288. package/plugin/channel/hub/committee-controller.ts +60 -0
  289. package/plugin/channel/hub/connections.ts +48 -0
  290. package/plugin/channel/hub/diagnostics.ts +67 -0
  291. package/plugin/channel/hub/envelope-validity.ts +109 -0
  292. package/plugin/channel/hub/envelopes.ts +66 -0
  293. package/plugin/channel/hub/lifecycle.ts +91 -0
  294. package/plugin/channel/hub/local-roads.ts +89 -0
  295. package/plugin/channel/hub/remote-roads.ts +165 -0
  296. package/plugin/channel/hub/road-controller.ts +99 -0
  297. package/plugin/channel/hub-client.ts +149 -0
  298. package/plugin/channel/local-hub.js +5995 -0
  299. package/plugin/channel/local-hub.ts +349 -0
  300. package/plugin/channel/map-reporter.ts +48 -0
  301. package/plugin/channel/package-lock.json +1731 -0
  302. package/plugin/channel/package.json +19 -0
  303. package/plugin/channel/protocol.ts +98 -0
  304. package/plugin/channel/road-cli.ts +18 -0
  305. package/plugin/channel/run.sh +15 -0
  306. package/plugin/channel/runtime/claude.ts +458 -0
  307. package/plugin/channel/runtime/codex-config.ts +77 -0
  308. package/plugin/channel/runtime/codex.ts +669 -0
  309. package/plugin/channel/runtime/command.ts +62 -0
  310. package/plugin/channel/runtime/factory.ts +15 -0
  311. package/plugin/channel/runtime/json-rpc.ts +154 -0
  312. package/plugin/channel/runtime/kimi.ts +277 -0
  313. package/plugin/channel/runtime/opencode.ts +248 -0
  314. package/plugin/channel/runtime/process.ts +84 -0
  315. package/plugin/channel/runtime/types.ts +45 -0
  316. package/plugin/channel/runtime-files.ts +73 -0
  317. package/plugin/channel/runtime-gateway.js +6396 -0
  318. package/plugin/channel/runtime-gateway.ts +358 -0
  319. package/plugin/channel/runtime-metrics.ts +52 -0
  320. package/plugin/channel/runtime-subscription.ts +180 -0
  321. package/plugin/channel/terminal-delivery.ts +131 -0
  322. package/plugin/channel/untrusted.ts +60 -0
  323. package/plugin/commands/committee.md +31 -0
  324. package/plugin/commands/exit.md +32 -0
  325. package/plugin/commands/goals.md +20 -0
  326. package/plugin/commands/join.md +18 -0
  327. package/plugin/commands/notice.md +20 -0
  328. package/plugin/commands/propose.md +19 -0
  329. package/plugin/commands/round.md +17 -0
  330. package/plugin/commands/session.md +22 -0
  331. package/plugin/commands/settings.md +25 -0
  332. package/plugin/commands/setup.md +21 -0
  333. package/plugin/commands/team.md +23 -0
  334. package/plugin/domains/custom.md +27 -0
  335. package/plugin/domains/finance.md +31 -0
  336. package/plugin/domains/healthcare.md +34 -0
  337. package/plugin/domains/legal.md +31 -0
  338. package/plugin/domains/marketing.md +33 -0
  339. package/plugin/domains/operations.md +31 -0
  340. package/plugin/domains/research.md +32 -0
  341. package/plugin/domains/sales.md +30 -0
  342. package/plugin/domains/software.md +37 -0
  343. package/plugin/hooks/activity.sh +6 -0
  344. package/plugin/hooks/digging.sh +68 -0
  345. package/plugin/hooks/growth.sh +42 -0
  346. package/plugin/hooks/hooks.json +106 -0
  347. package/plugin/hooks/notice-on-pr.sh +35 -0
  348. package/plugin/hooks/notice-on-stop.sh +80 -0
  349. package/plugin/hooks/notice-pending.sh +58 -0
  350. package/plugin/hooks/solo-en-ciudad.sh +23 -0
  351. package/plugin/hooks/tokens.sh +47 -0
  352. package/plugin/roles/examples/account-executive.md +25 -0
  353. package/plugin/roles/examples/ai-manager.md +34 -0
  354. package/plugin/roles/examples/associate.md +24 -0
  355. package/plugin/roles/examples/brand-lead.md +20 -0
  356. package/plugin/roles/examples/cfo.md +20 -0
  357. package/plugin/roles/examples/city-lead.md +25 -0
  358. package/plugin/roles/examples/clinical-director.md +26 -0
  359. package/plugin/roles/examples/clinical-ops.md +25 -0
  360. package/plugin/roles/examples/clinician.md +25 -0
  361. package/plugin/roles/examples/compliance.md +27 -0
  362. package/plugin/roles/examples/content.md +24 -0
  363. package/plugin/roles/examples/controller.md +27 -0
  364. package/plugin/roles/examples/cpto.md +41 -0
  365. package/plugin/roles/examples/customer-success.md +25 -0
  366. package/plugin/roles/examples/data-engineer.md +37 -0
  367. package/plugin/roles/examples/data.md +60 -0
  368. package/plugin/roles/examples/dev.md +37 -0
  369. package/plugin/roles/examples/devops.md +39 -0
  370. package/plugin/roles/examples/enablement.md +24 -0
  371. package/plugin/roles/examples/ethics.md +24 -0
  372. package/plugin/roles/examples/fin-analytics.md +27 -0
  373. package/plugin/roles/examples/health-compliance.md +25 -0
  374. package/plugin/roles/examples/health-data.md +26 -0
  375. package/plugin/roles/examples/knowledge.md +25 -0
  376. package/plugin/roles/examples/lifecycle.md +26 -0
  377. package/plugin/roles/examples/llm-engineer.md +34 -0
  378. package/plugin/roles/examples/managing-partner.md +20 -0
  379. package/plugin/roles/examples/methods.md +23 -0
  380. package/plugin/roles/examples/operations-lead.md +23 -0
  381. package/plugin/roles/examples/ops.md +24 -0
  382. package/plugin/roles/examples/patient-safety.md +26 -0
  383. package/plugin/roles/examples/performance.md +27 -0
  384. package/plugin/roles/examples/po.md +36 -0
  385. package/plugin/roles/examples/process-owner.md +23 -0
  386. package/plugin/roles/examples/product-design.md +39 -0
  387. package/plugin/roles/examples/program-manager.md +24 -0
  388. package/plugin/roles/examples/quality.md +25 -0
  389. package/plugin/roles/examples/research-director.md +24 -0
  390. package/plugin/roles/examples/research-ops.md +25 -0
  391. package/plugin/roles/examples/researcher.md +25 -0
  392. package/plugin/roles/examples/revenue-lead.md +25 -0
  393. package/plugin/roles/examples/revops.md +26 -0
  394. package/plugin/roles/examples/seo.md +27 -0
  395. package/plugin/roles/examples/specialist.md +24 -0
  396. package/plugin/scripts/admision.py +165 -0
  397. package/plugin/scripts/apaga.py +232 -0
  398. package/plugin/scripts/avatar.py +211 -0
  399. package/plugin/scripts/broker.py +531 -0
  400. package/plugin/scripts/cage.py +252 -0
  401. package/plugin/scripts/capabilities.py +197 -0
  402. package/plugin/scripts/card.py +312 -0
  403. package/plugin/scripts/cities.py +520 -0
  404. package/plugin/scripts/city-env.sh +90 -0
  405. package/plugin/scripts/city-runtime.sh +87 -0
  406. package/plugin/scripts/city-session.sh +526 -0
  407. package/plugin/scripts/city_env.py +67 -0
  408. package/plugin/scripts/crecimiento.py +140 -0
  409. package/plugin/scripts/deliberations.py +48 -0
  410. package/plugin/scripts/doctor.py +172 -0
  411. package/plugin/scripts/domains.py +201 -0
  412. package/plugin/scripts/evidencia.py +64 -0
  413. package/plugin/scripts/find-repos.sh +111 -0
  414. package/plugin/scripts/gh.py +159 -0
  415. package/plugin/scripts/hall_protocol.py +147 -0
  416. package/plugin/scripts/hook_activity.py +139 -0
  417. package/plugin/scripts/launch.py +94 -0
  418. package/plugin/scripts/logs.py +98 -0
  419. package/plugin/scripts/pairing.py +185 -0
  420. package/plugin/scripts/parcels.py +132 -0
  421. package/plugin/scripts/read-card.py +51 -0
  422. package/plugin/scripts/report.py +201 -0
  423. package/plugin/scripts/reset.py +180 -0
  424. package/plugin/scripts/roads.py +230 -0
  425. package/plugin/scripts/roles.py +274 -0
  426. package/plugin/scripts/runtime_log.py +69 -0
  427. package/plugin/scripts/runtime_processes.py +132 -0
  428. package/plugin/scripts/rutas.py +83 -0
  429. package/plugin/scripts/seat.py +1349 -0
  430. package/plugin/scripts/tokens.py +194 -0
  431. package/plugin/scripts/trust-repos.py +59 -0
  432. package/plugin/scripts/ui.py +280 -0
  433. package/plugin/scripts/units.py +80 -0
  434. package/plugin/scripts/workspace.py +364 -0
  435. package/plugin/skills/city/SKILL.md +191 -0
  436. package/templates/blank.md +39 -0
  437. package/templates/finance.md +44 -0
  438. package/templates/legal.md +45 -0
  439. package/templates/marketing.md +48 -0
  440. package/templates/product.md +45 -0
@@ -0,0 +1,487 @@
1
+ #!/usr/bin/env python3
2
+ """Stress one city with mixed Claude stream-json and Codex app-server actors."""
3
+ import argparse
4
+ import json
5
+ import math
6
+ import os
7
+ import shutil
8
+ import signal
9
+ import socket
10
+ import subprocess
11
+ import sys
12
+ import tempfile
13
+ import time
14
+ import urllib.parse
15
+
16
+ HERE = os.path.dirname(os.path.abspath(__file__))
17
+ ROOT = os.path.dirname(os.path.dirname(HERE))
18
+ GATEWAY = os.path.join(ROOT, 'plugin', 'channel', 'runtime-gateway.js')
19
+ CLIENT = os.path.join(ROOT, 'plugin', 'channel', 'client.js')
20
+ FAKE = os.path.join(ROOT, 'benchmarks', 'latency', 'fake-native-server.mjs')
21
+ CLAUDE_FAKE = os.path.join(ROOT, 'benchmarks', 'latency', 'fake-claude-cli.mjs')
22
+ CHANNEL_DIR = os.path.join(ROOT, 'plugin', 'channel')
23
+ SNAPSHOT_JS = r"""
24
+ const WebSocket = require('ws');
25
+ const ws = new WebSocket(process.argv[1], {origin:'http://127.0.0.1:43111'});
26
+ const timer = setTimeout(() => process.exit(3), 5000);
27
+ ws.on('message', raw => {
28
+ let value; try { value = JSON.parse(String(raw)); } catch { return; }
29
+ if (value.type !== 'activity.state') return;
30
+ clearTimeout(timer); process.stdout.write(JSON.stringify(value)); ws.close();
31
+ setTimeout(() => process.exit(0), 10);
32
+ });
33
+ ws.on('error', () => {});
34
+ """
35
+
36
+
37
+ def parse_args():
38
+ parser = argparse.ArgumentParser(
39
+ description='Exercise one local city with an even Claude/Codex mix.')
40
+ parser.add_argument('--agents', type=int, default=40,
41
+ help='total actors; must be even (default: 40)')
42
+ parser.add_argument('--rounds', type=int, default=2,
43
+ help='fan-out rounds; round two follows a forced hub restart (default: 2)')
44
+ parser.add_argument('--timeout', type=int, default=20,
45
+ help='seconds allowed per startup or fan-out phase (default: 20)')
46
+ parser.add_argument('--json', action='store_true', help='print only JSON')
47
+ parser.add_argument('--keep', action='store_true', help='keep temporary artifacts')
48
+ args = parser.parse_args()
49
+ if args.agents < 2 or args.agents % 2:
50
+ parser.error('--agents must be an even integer of at least 2')
51
+ if args.rounds < 1:
52
+ parser.error('--rounds must be at least 1')
53
+ return args
54
+
55
+
56
+ def main():
57
+ args = parse_args()
58
+ report = run(args)
59
+ if args.json:
60
+ print(json.dumps(report, sort_keys=True))
61
+ else:
62
+ print_report(report)
63
+ return 0 if report.get('status') == 'pass' else 1
64
+
65
+
66
+ def run(args):
67
+ report = {
68
+ 'schema': 'agents-city/mixed-runtime-stress@1',
69
+ 'agents': args.agents,
70
+ 'mix': {'claude-stream-json': args.agents // 2,
71
+ 'codex-app-server-ws': args.agents // 2},
72
+ 'rounds': [],
73
+ 'liveFeed': {'transport': 'authenticated-read-only-websocket'},
74
+ 'status': 'failed',
75
+ }
76
+ base = tempfile.mkdtemp(prefix='agents-city-stress-')
77
+ app = os.path.join(base, 'app')
78
+ city = os.path.join(app, 'stress', 'forty')
79
+ repos = os.path.join(base, 'repos')
80
+ fake_bin = os.path.join(base, 'bin')
81
+ os.makedirs(city)
82
+ os.makedirs(repos)
83
+ os.makedirs(fake_bin)
84
+ fake_claude = os.path.join(fake_bin, 'claude')
85
+ os.symlink(CLAUDE_FAKE, fake_claude)
86
+ actors = mixed_actors(args.agents)
87
+ write_city(city, actors)
88
+ for _, actor in actors:
89
+ os.makedirs(os.path.join(repos, actor))
90
+ port = free_port()
91
+ codex_capture = os.path.join(base, 'codex.jsonl')
92
+ claude_capture = os.path.join(base, 'claude.jsonl')
93
+ claude_behavior = os.path.join(base, 'claude.behavior')
94
+ open(claude_behavior, 'w', encoding='utf-8').write('healthy\n')
95
+ env = dict(
96
+ os.environ,
97
+ AGENTS_CITY_HOME=app,
98
+ AGENTS_CITY_DATA=city,
99
+ AGENTS_CITY_USER='stress',
100
+ CITY_RUNTIME_AUTO='1',
101
+ CITY_BUS_DEBUG='1',
102
+ CITY_CODEX_APP_SERVER_URL=f'ws://127.0.0.1:{port}',
103
+ CITY_CLAUDE_FAKE_CAPTURE=claude_capture,
104
+ CITY_CLAUDE_FAKE_BEHAVIOR=claude_behavior,
105
+ CITY_CLAUDE_STARTUP_TIMEOUT_MS='2000',
106
+ )
107
+ for key in ('CITY_BUS_URL', 'CITY_BUS_TOKEN', 'CITY_DIR'):
108
+ env.pop(key, None)
109
+ runtime_dir = os.path.join(app, '.runtime', 'bus', 'city-forty')
110
+ endpoint_path = os.path.join(runtime_dir, 'endpoint.json')
111
+ metrics_path = os.path.join(runtime_dir, 'runtime-latency.jsonl')
112
+ hub_log = os.path.join(runtime_dir, 'hub.log')
113
+ gateways = []
114
+ gateway_streams = []
115
+ fake = None
116
+ started = time.monotonic()
117
+ try:
118
+ fake = subprocess.Popen(
119
+ ['node', FAKE, 'codex', str(port), codex_capture],
120
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
121
+ if not wait_port(port, args.timeout):
122
+ raise RuntimeError('the Codex app-server double did not start')
123
+
124
+ for runtime, actor in actors:
125
+ log = open(os.path.join(base, f'{actor}.log'), 'w+', encoding='utf-8')
126
+ gateway_streams.append(log)
127
+ command = fake_claude if runtime == 'claude' else 'codex'
128
+ gateways.append(subprocess.Popen(
129
+ ['node', GATEWAY, '--data', city, '--actor', actor,
130
+ '--cwd', os.path.join(repos, actor), '--command', command,
131
+ '--auto', '1', '--interactive', '0'],
132
+ env=env, stdin=subprocess.DEVNULL,
133
+ stdout=log, stderr=log, text=True))
134
+
135
+ addresses = [f'connected runtime:{actor}' for _, actor in actors]
136
+ if not wait_for(lambda: all(marker in read(hub_log) for marker in addresses),
137
+ args.timeout):
138
+ raise RuntimeError('not all mixed runtimes authenticated on the city hub')
139
+ if any(process.poll() is not None for process in gateways):
140
+ raise RuntimeError('a Codex gateway exited during startup')
141
+ startup_hubs = hub_pids(city)
142
+ if len(startup_hubs) != 1:
143
+ raise RuntimeError(f'concurrent startup created {len(startup_hubs)} hubs: {startup_hubs}')
144
+ report['startupMs'] = round((time.monotonic() - started) * 1000)
145
+ report['startupHubProcesses'] = len(startup_hubs)
146
+
147
+ participants = [actor for _, actor in actors]
148
+ for round_number in range(1, args.rounds + 1):
149
+ if round_number == 2:
150
+ current = hub_pids(city)
151
+ stopped_pid = current[0] if len(current) == 1 else 0
152
+ if not stopped_pid or not stop_pid(stopped_pid):
153
+ raise RuntimeError(f'could not force the single hub outage: {current}')
154
+ # Forty reconnecting clients can elect the replacement before
155
+ # this process observes the brief unlink. That is successful
156
+ # recovery, not a stale endpoint. What must disappear is the
157
+ # stopped PID's ownership; the endpoint may validly already name
158
+ # the one new hub.
159
+ if not wait_for(
160
+ lambda: endpoint_released_or_replaced(endpoint_path, stopped_pid), 4
161
+ ):
162
+ raise RuntimeError('the stopped hub still owns the endpoint')
163
+ round_started = time.monotonic()
164
+ thread = open_committee(participants, round_number, env)
165
+ if not thread:
166
+ raise RuntimeError(f'round {round_number} did not open a committee')
167
+ feed = spectator_snapshot(endpoint_path)
168
+ opened_feed = [
169
+ event for event in feed.get('events', [])
170
+ if event.get('thread') == thread
171
+ and event.get('kind') == 'committee.opened'
172
+ ]
173
+ invited = '\n'.join(
174
+ str(detail) for event in opened_feed
175
+ for detail in event.get('details', []))
176
+ if len(opened_feed) != 1 or not all(actor in invited for actor in participants):
177
+ raise RuntimeError(
178
+ f'round {round_number} spectator feed did not represent all '
179
+ f'{args.agents} selected actors exactly once')
180
+ if not wait_for(
181
+ lambda: round_complete(thread, claude_capture, codex_capture, metrics_path,
182
+ args.agents // 2, args.agents),
183
+ args.timeout,
184
+ ):
185
+ raise RuntimeError(
186
+ f'round {round_number} did not reach all {args.agents} native runtimes')
187
+ if not wait_for(lambda: outboxes_empty(runtime_dir, participants), 3):
188
+ raise RuntimeError(f'round {round_number} left a durable outbox undrained')
189
+ fanout_ms = round((time.monotonic() - round_started) * 1000)
190
+ time.sleep(.35)
191
+ claude_count = len(records_for_thread(claude_capture, thread))
192
+ codex_count = len(records_for_thread(codex_capture, thread))
193
+ metrics = [row for row in read_jsonl(metrics_path) if row.get('thread') == thread]
194
+ if claude_count != args.agents // 2 or codex_count != args.agents // 2:
195
+ raise RuntimeError(
196
+ f'round {round_number} duplicated delivery: '
197
+ f'Claude={claude_count}, Codex={codex_count}')
198
+ if len(metrics) != args.agents:
199
+ raise RuntimeError(
200
+ f'round {round_number} recorded {len(metrics)} acceptances, expected {args.agents}')
201
+ latencies = sorted(int(row.get('totalToNativeAcceptMs', 0)) for row in metrics)
202
+ round_report = {
203
+ 'round': round_number,
204
+ 'thread': thread,
205
+ 'fanoutMs': fanout_ms,
206
+ 'accepted': len(metrics),
207
+ 'claudeDeliveries': claude_count,
208
+ 'codexDeliveries': codex_count,
209
+ 'p95NativeAcceptMs': percentile(latencies, .95),
210
+ 'maxNativeAcceptMs': max(latencies),
211
+ 'hubProcesses': len(hub_pids(city)),
212
+ 'liveFeedOpenEvents': len(opened_feed),
213
+ 'liveFeedParticipants': args.agents,
214
+ }
215
+ if round_report['hubProcesses'] != 1:
216
+ raise RuntimeError(
217
+ f'round {round_number} ended with {round_report["hubProcesses"]} hubs')
218
+ if round_report['p95NativeAcceptMs'] >= 5_000 \
219
+ or round_report['maxNativeAcceptMs'] >= 10_000:
220
+ raise RuntimeError(f'round {round_number} exceeded the local latency budget')
221
+ report['rounds'].append(round_report)
222
+
223
+ all_metrics = read_jsonl(metrics_path)
224
+ expected = args.agents * args.rounds
225
+ if len(all_metrics) != expected:
226
+ raise RuntimeError(f'latency ledger has {len(all_metrics)} rows, expected {expected}')
227
+ transports = {}
228
+ for row in all_metrics:
229
+ transport = row.get('transport', '')
230
+ transports[transport] = transports.get(transport, 0) + 1
231
+ report['transportAcceptances'] = transports
232
+ report['duplicateDeliveries'] = 0
233
+ report['remainingOutboxes'] = count_outboxes(runtime_dir, participants)
234
+ report['terminalInjection'] = os.path.exists(os.path.join(runtime_dir, 'adapters'))
235
+ report['liveFeed'].update({
236
+ 'openEvents': sum(row['liveFeedOpenEvents'] for row in report['rounds']),
237
+ 'participantsPerRound': [row['liveFeedParticipants'] for row in report['rounds']],
238
+ 'survivedHubRestart': args.rounds < 2 or len(report['rounds']) >= 2,
239
+ 'duplicates': 0,
240
+ })
241
+ if report['remainingOutboxes'] or report['terminalInjection']:
242
+ raise RuntimeError('stress run used terminal injection or left queued work')
243
+ report['status'] = 'pass'
244
+ except Exception as error:
245
+ report['error'] = str(error)
246
+ finally:
247
+ stop_processes(gateways)
248
+ if fake:
249
+ stop_process(fake)
250
+ for stream in gateway_streams:
251
+ stream.close()
252
+ for pid in hub_pids(city):
253
+ stop_pid(pid)
254
+ remaining = hub_pids(city)
255
+ report['cleanup'] = {
256
+ 'remainingHubProcesses': remaining,
257
+ 'remainingRuntimeProcesses': sum(
258
+ process.poll() is None for process in gateways),
259
+ }
260
+ if remaining or report['cleanup']['remainingRuntimeProcesses']:
261
+ report['status'] = 'failed'
262
+ report['error'] = 'stress cleanup left an orphan process'
263
+ if args.keep:
264
+ report['artifacts'] = base
265
+ else:
266
+ shutil.rmtree(base, ignore_errors=True)
267
+ return report
268
+
269
+
270
+ def mixed_actors(total):
271
+ actors = []
272
+ for index in range(total // 2):
273
+ actors.append(('claude', f'claude-{index + 1:02d}'))
274
+ actors.append(('codex', f'codex-{index + 1:02d}'))
275
+ return actors
276
+
277
+
278
+ def write_city(city, actors):
279
+ open(os.path.join(city, 'city.yml'), 'w', encoding='utf-8').write(
280
+ 'id: city_forty\nname: Forty\nslug: forty\nowner: stress\n')
281
+ open(os.path.join(city, 'roads.json'), 'w', encoding='utf-8').write(
282
+ '{"version": 1, "roads": []}\n')
283
+ names = [actor for _, actor in actors]
284
+ card = '---\nuser: stress\nagent: stress/ceo\nrepos: [' + ', '.join(names) + ']\n'
285
+ for runtime, actor in actors:
286
+ card += f'role.{actor}: blank\nruns.{actor}: {runtime}\n'
287
+ open(os.path.join(city, 'stress.md'), 'w', encoding='utf-8').write(card + '---\n')
288
+
289
+
290
+ def open_committee(participants, round_number, env):
291
+ result = subprocess.run(
292
+ ['node', CLIENT, 'committee', 'open', '--input', '-'],
293
+ input=json.dumps({
294
+ 'question': f'Mixed-runtime stress round {round_number}.',
295
+ 'desiredOutcome': 'Every selected runtime accepts one isolated assignment.',
296
+ 'definitionOfDone': [
297
+ f'{len(participants)} native acceptances with no duplicate delivery'],
298
+ 'participants': participants,
299
+ }), capture_output=True, text=True,
300
+ env=dict(env, CITY_BUS_ACTOR='seat'), timeout=15)
301
+ if result.returncode != 0:
302
+ raise RuntimeError(result.stderr.strip() or 'committee.open failed')
303
+ return json.loads(result.stdout).get('id', '')
304
+
305
+
306
+ def spectator_snapshot(endpoint_path):
307
+ endpoint = json.load(open(endpoint_path, encoding='utf-8'))
308
+ parsed = urllib.parse.urlparse(endpoint['url'])
309
+ query = urllib.parse.urlencode({
310
+ 'mode': 'spectator', 'token': endpoint['spectatorToken']})
311
+ url = urllib.parse.urlunparse(parsed._replace(query=query))
312
+ result = subprocess.run(
313
+ ['node', '--input-type=commonjs', '-e', SNAPSHOT_JS, url],
314
+ cwd=CHANNEL_DIR, capture_output=True, text=True, timeout=7)
315
+ if result.returncode != 0:
316
+ raise RuntimeError(
317
+ f'spectator WebSocket failed: {result.stderr.strip() or result.returncode}')
318
+ try:
319
+ return json.loads(result.stdout)
320
+ except json.JSONDecodeError as error:
321
+ raise RuntimeError(f'spectator WebSocket returned invalid JSON: {error}') from error
322
+
323
+
324
+ def round_complete(thread, claude_capture, codex_capture, metrics_path, half, total):
325
+ return (
326
+ len(records_for_thread(claude_capture, thread)) == half
327
+ and len(records_for_thread(codex_capture, thread)) == half
328
+ and len([row for row in read_jsonl(metrics_path)
329
+ if row.get('thread') == thread]) == total
330
+ )
331
+
332
+
333
+ def records_for_thread(path, thread):
334
+ return [row for row in read_jsonl(path) if thread in json.dumps(row)]
335
+
336
+
337
+ def read_jsonl(path):
338
+ if not os.path.exists(path):
339
+ return []
340
+ rows = []
341
+ with open(path, encoding='utf-8') as stream:
342
+ for line in stream:
343
+ try:
344
+ rows.append(json.loads(line))
345
+ except json.JSONDecodeError:
346
+ pass
347
+ return rows
348
+
349
+
350
+ def read(path):
351
+ try:
352
+ return open(path, encoding='utf-8', errors='replace').read()
353
+ except OSError:
354
+ return ''
355
+
356
+
357
+ def count_outboxes(runtime_dir, participants):
358
+ root = os.path.join(runtime_dir, 'outbox')
359
+ return sum(
360
+ len(os.listdir(os.path.join(root, actor)))
361
+ for actor in participants
362
+ if os.path.isdir(os.path.join(root, actor))
363
+ )
364
+
365
+
366
+ def outboxes_empty(runtime_dir, participants):
367
+ return count_outboxes(runtime_dir, participants) == 0
368
+
369
+
370
+ def endpoint_released_or_replaced(path, stopped_pid):
371
+ if not os.path.exists(path):
372
+ return True
373
+ try:
374
+ endpoint = json.load(open(path, encoding='utf-8'))
375
+ replacement = int(endpoint.get('pid', 0))
376
+ return replacement > 0 and replacement != stopped_pid and alive(replacement)
377
+ except (OSError, ValueError, TypeError, json.JSONDecodeError):
378
+ return False
379
+
380
+
381
+ def percentile(values, ratio):
382
+ return values[max(0, math.ceil(len(values) * ratio) - 1)] if values else None
383
+
384
+
385
+ def free_port():
386
+ with socket.socket() as sock:
387
+ sock.bind(('127.0.0.1', 0))
388
+ return sock.getsockname()[1]
389
+
390
+
391
+ def wait_port(port, timeout):
392
+ return wait_for(lambda: port_open(port), timeout)
393
+
394
+
395
+ def port_open(port):
396
+ try:
397
+ with socket.create_connection(('127.0.0.1', port), timeout=.2):
398
+ return True
399
+ except OSError:
400
+ return False
401
+
402
+
403
+ def wait_for(condition, timeout):
404
+ deadline = time.monotonic() + timeout
405
+ while time.monotonic() < deadline:
406
+ if condition():
407
+ return True
408
+ time.sleep(.05)
409
+ return False
410
+
411
+
412
+ def hub_pids(city):
413
+ target = os.path.realpath(city)
414
+ result = subprocess.run(
415
+ ['ps', '-axo', 'pid=,command='], capture_output=True, text=True)
416
+ marker = 'local-hub.js --data '
417
+ pids = []
418
+ for line in result.stdout.splitlines():
419
+ if marker not in line:
420
+ continue
421
+ raw_pid, command = line.strip().split(None, 1)
422
+ supplied = command.split(marker, 1)[1].strip()
423
+ if os.path.realpath(supplied) == target:
424
+ pids.append(int(raw_pid))
425
+ return sorted(set(pids))
426
+
427
+
428
+ def stop_pid(pid, timeout=3):
429
+ if not pid:
430
+ return True
431
+ try:
432
+ os.kill(pid, signal.SIGTERM)
433
+ except ProcessLookupError:
434
+ return True
435
+ if wait_for(lambda: not alive(pid), timeout):
436
+ return True
437
+ try:
438
+ os.kill(pid, signal.SIGKILL)
439
+ except ProcessLookupError:
440
+ return True
441
+ return wait_for(lambda: not alive(pid), 1)
442
+
443
+
444
+ def alive(pid):
445
+ try:
446
+ os.kill(pid, 0)
447
+ return True
448
+ except ProcessLookupError:
449
+ return False
450
+
451
+
452
+ def stop_process(process):
453
+ if process.poll() is not None:
454
+ return
455
+ process.terminate()
456
+ try:
457
+ process.wait(timeout=3)
458
+ except subprocess.TimeoutExpired:
459
+ process.kill()
460
+ process.wait(timeout=2)
461
+
462
+
463
+ def stop_processes(processes):
464
+ for process in processes:
465
+ if process.poll() is None:
466
+ process.terminate()
467
+ for process in processes:
468
+ try:
469
+ process.wait(timeout=3)
470
+ except subprocess.TimeoutExpired:
471
+ process.kill()
472
+ process.wait(timeout=2)
473
+
474
+
475
+ def print_report(report):
476
+ print(f"\n mixed runtime stress: {report['agents']} agents")
477
+ print(f" status: {report['status']}")
478
+ for row in report.get('rounds', []):
479
+ print(
480
+ f" round {row['round']}: {row['accepted']} accepted in {row['fanoutMs']}ms "
481
+ f"(p95 {row['p95NativeAcceptMs']}ms, max {row['maxNativeAcceptMs']}ms)")
482
+ if report.get('error'):
483
+ print(f" error: {report['error']}")
484
+
485
+
486
+ if __name__ == '__main__':
487
+ sys.exit(main())
package/bin/agents ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # List agents and manage their workspace mounts (symlinks to repos, worktrees
3
+ # or document folders). Agent-first: a repo is just one thing an agent mounts.
4
+ exec python3 "$(cd "$(dirname "$0")/../plugin/scripts" && pwd)/workspace.py" "$@"
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ /* The npm front door: `npm install -g agents-city`, then `agents-city`.
3
+ *
4
+ * The same trick Claude Code uses — npm as the installer, the payload being
5
+ * whatever the product actually is. Here the payload is the repo itself: the
6
+ * Python that writes your files, the bash that builds your tmux session, and the
7
+ * prebuilt map. Node is only the dispatcher, and it is guaranteed present because
8
+ * npm just ran.
9
+ *
10
+ * Deliberately not a port to JS: the shared modules (card, parcels, units, roles)
11
+ * are the product's single source of truth and every door reads them — the
12
+ * installed Claude plugin included. Two languages holding one truth is the drift
13
+ * disease this repo already caught once and killed.
14
+ */
15
+ 'use strict';
16
+ const { spawnSync } = require('child_process');
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+
20
+ const RAIZ = path.dirname(__dirname);
21
+
22
+ const ORDENES = {
23
+ hall: { que: ['bin/hall'], di: 'the town hall: manage your city from the browser' },
24
+ seat: { que: ['bin/seat'], di: 'your chair: domain, seat role, repo-agent roles, goal, tmux' },
25
+ cities: { que: ['bin/cities'], di: 'list, create or select your cities' },
26
+ agents: { que: ['bin/agents'], di: 'list agents and manage their workspace mounts' },
27
+ road: { que: ['bin/road'], di: 'connect two cities explicitly' },
28
+ bus: { que: ['bin/bus'], di: 'send information over those roads (seat only)' },
29
+ committee: { que: ['bin/committee'], di: 'chair-mediated work with repo agents' },
30
+ benchmark: { que: ['bin/benchmark'], di: 'offline stress/governance or opt-in live runtime latency' },
31
+ logs: { que: ['bin/logs'], di: 'read or follow visible activity and operational diagnostics' },
32
+ reset: { que: ['bin/reset'], di: 'reset one city to onboarding, recoverably' },
33
+ skills: { que: ['bin/skills'], di: 'recognise the skills installed in each repo' },
34
+ city: { que: ['bin/city'], di: 'draw your city' },
35
+ demo: { que: ['bin/demo'], di: 'Aurora Games — see it working, no account' },
36
+ setup: { que: ['bin/setup.py'], di: 'create or open a personal city' },
37
+ report: { que: ['bin/report.py'], di: 'report growth from your folders' },
38
+ tokens: { que: ['bin/tokens.py'], di: 'report token spend' },
39
+ exit: { que: ['bin/exit'], di: 'close the day — every session and agent, or one city' },
40
+ test: { que: ['bin/test'], di: 'every check in the repo' },
41
+ };
42
+
43
+ function ayuda() {
44
+ const v = require(path.join(RAIZ, 'package.json')).version;
45
+ console.log(`
46
+ agents-city ${v} — autonomous cities for your coding agents
47
+
48
+ usage: agents-city [command] [args]
49
+
50
+ With no command it opens the town hall.
51
+ `);
52
+ for (const [n, o] of Object.entries(ORDENES)) {
53
+ console.log(` ${n.padEnd(11)}${o.di}`);
54
+ }
55
+ console.log(`
56
+ Needs python3 and tmux (the seat installs tmux itself). On Windows, run it
57
+ inside WSL — the session is made of tmux windows, and there is no native tmux.
58
+ `);
59
+ }
60
+
61
+ function hay(programa) {
62
+ const r = spawnSync(process.platform === 'win32' ? 'where' : 'command',
63
+ process.platform === 'win32' ? [programa] : ['-v', programa],
64
+ { shell: process.platform !== 'win32', stdio: 'ignore' });
65
+ return r.status === 0;
66
+ }
67
+
68
+ function main() {
69
+ const args = process.argv.slice(2);
70
+ const primera = args[0] || 'hall';
71
+
72
+ if (primera === '--help' || primera === '-h' || primera === 'help') { ayuda(); return 0; }
73
+ if (primera === '--version' || primera === '-v') {
74
+ console.log(require(path.join(RAIZ, 'package.json')).version);
75
+ return 0;
76
+ }
77
+
78
+ if (process.platform === 'win32') {
79
+ console.error('\n agents-city runs on tmux and bash, and Windows has neither');
80
+ console.error(' natively. Install it inside WSL and run it from there:\n');
81
+ console.error(' wsl -- npm install -g agents-city\n');
82
+ return 1;
83
+ }
84
+
85
+ const orden = ORDENES[primera];
86
+ const resto = orden ? args.slice(1) : args;
87
+ const entrada = orden ? orden.que[0] : ORDENES.hall.que[0];
88
+ if (!orden && args.length && primera.startsWith('-') === false) {
89
+ console.error(`\n No such command: ${primera}\n`);
90
+ ayuda();
91
+ return 1;
92
+ }
93
+
94
+ if (!hay('python3')) {
95
+ console.error('\n agents-city needs python3 on PATH — the files it manages are');
96
+ console.error(' written by Python, the same modules every door shares.');
97
+ console.error(' macOS: xcode-select --install · Debian: apt install python3\n');
98
+ return 1;
99
+ }
100
+
101
+ const guion = path.join(RAIZ, entrada);
102
+ if (!fs.existsSync(guion)) {
103
+ console.error(`\n ${entrada} is missing from this install — reinstall:`);
104
+ console.error(' npm install -g agents-city\n');
105
+ return 1;
106
+ }
107
+ // Unbuffered, so the URL a server prints reaches a pipe as well as a terminal
108
+ // — a CI log or a `| head` otherwise swallows it and the run looks silent.
109
+ const r = spawnSync(guion, orden ? resto : args, {
110
+ stdio: 'inherit', cwd: RAIZ,
111
+ env: { ...process.env, PYTHONUNBUFFERED: '1' },
112
+ });
113
+ return r.status === null ? 1 : r.status;
114
+ }
115
+
116
+ process.exit(main());
package/bin/benchmark ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ # Benchmarks are explicit: stress/committee are offline, live uses model accounts.
3
+ set -euo pipefail
4
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
+ case "${1:-}" in
6
+ live) shift; exec python3 "$ROOT/benchmarks/latency/live.py" "$@" ;;
7
+ committee) shift; exec python3 "$ROOT/benchmarks/committee/run.py" "$@" ;;
8
+ stress) shift; exec python3 "$ROOT/benchmarks/stress/run.py" "$@" ;;
9
+ -h|--help|'')
10
+ echo "usage: agents-city benchmark <stress | live | committee> [options]"
11
+ echo
12
+ echo " stress offline 40-agent Claude/Codex fan-out and reconnect stress"
13
+ echo " live real bus -> native model -> committee-position latency (uses accounts/credits)"
14
+ echo " committee offline structural governance benchmark"
15
+ ;;
16
+ *) echo "unknown benchmark: $1" >&2; exit 2 ;;
17
+ esac
package/bin/bus ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ # Send or inspect messages on explicit roads. The hub enforces seat-only ACLs.
3
+ set -euo pipefail
4
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
+ . "$ROOT/plugin/scripts/city-env.sh"
6
+ exec node "$ROOT/plugin/channel/client.js" bus "$@"
package/bin/cities ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # List, create and select this user's local cities.
3
+ exec python3 "$(cd "$(dirname "$0")/../plugin/scripts" && pwd)/cities.py" "$@"