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,232 @@
1
+ #!/usr/bin/env python3
2
+ """Close the whole day: the tmux sessions, every agent inside them, the hall,
3
+ the map. The seat's missing other half.
4
+
5
+ agents-city exit close everything, every city
6
+ agents-city exit home close ONE city — its session, its agents, its
7
+ map — and leave the others working
8
+ agents-city exit --dry-run say what would close, close nothing
9
+
10
+ `seat` builds a session with an agent per window and nothing ever tore it down:
11
+ detach, open another city, run the hall — and a machine fills with processes
12
+ nobody remembers starting. One session was found nineteen windows deep while
13
+ this was being written.
14
+
15
+ Precision matters more than reach here. This closes ONLY what the city started,
16
+ identified by marks the city itself leaves, and never by broad process-name
17
+ sweeps that would take down some other project's dev server:
18
+
19
+ * tmux sessions whose name is one this product minted — always user-city —
20
+ across every city this user owns. Your own
21
+ unrelated tmux sessions are not touched.
22
+ * the town hall, matched by its own entry point.
23
+ * the map and the demo, found by who holds the city's local database open
24
+ (`~/.agents-city/state`) — not by killing every wrangler on the machine.
25
+
26
+ Killing a session SIGHUPs its panes, which is how every Claude inside exits.
27
+ """
28
+ import argparse
29
+ import os
30
+ import signal
31
+ import subprocess
32
+ import sys
33
+ import time
34
+
35
+ AQUI = os.path.dirname(os.path.abspath(__file__))
36
+ sys.path.insert(0, AQUI)
37
+ import cities # noqa: E402
38
+ import runtime_processes # noqa: E402
39
+
40
+ ESTADO = os.path.join(cities.raiz(), 'state')
41
+
42
+
43
+ def _corre(args):
44
+ try:
45
+ r = subprocess.run(args, capture_output=True, text=True, timeout=30)
46
+ return r.stdout
47
+ except (OSError, subprocess.TimeoutExpired):
48
+ return ''
49
+
50
+
51
+ def sesiones_de_la_ciudad(solo=''):
52
+ """Every live tmux session this product would have minted, and only those.
53
+
54
+ Candidates come from city metadata: for each city this machine knows, the
55
+ owner-city session minted by cities.py. A session called anything else is
56
+ somebody's own work and stays alive. With `solo` set to one
57
+ city's folder, only that city's sessions qualify — closing the client's day
58
+ must not end the company's.
59
+ """
60
+ vivas = {l.strip() for l in _corre(['tmux', 'ls', '-F', '#{session_name}']).splitlines()
61
+ if l.strip()}
62
+ if not vivas:
63
+ return []
64
+ donde = ([{'ruta': solo}] if solo else cities.lista())
65
+ candidatas = set()
66
+ for ciudad in donde:
67
+ usuario = cities.lee_clave(ciudad['ruta'], 'owner')
68
+ if usuario:
69
+ candidatas.add(cities.sesion(usuario, ciudad['ruta']))
70
+ return sorted(vivas & candidatas)
71
+
72
+
73
+ def servidores():
74
+ """The Hall and an in-flight setup launcher, by their own entry points —
75
+ never a pattern loose enough to match somebody else's Python."""
76
+ pids = []
77
+ for patron in ('bin/hall', 'bin/setup.py'):
78
+ salida = _corre(['pgrep', '-f', patron])
79
+ pids += [int(p) for p in salida.split() if p.strip().isdigit()]
80
+ return sorted(set(pids) - {os.getpid(), os.getppid()})
81
+
82
+
83
+ def mapas(solo=''):
84
+ """Whoever holds the city's local database open: wrangler, workerd, the demo.
85
+ lsof on our own state directory is exact — it cannot name another project's
86
+ dev server, because no other project has these files open. With `solo`, only
87
+ that city's slice of the state; the demo counts as everything-only."""
88
+ donde = os.path.join(ESTADO, cities.slug(solo)) if solo else ESTADO
89
+ if not os.path.isdir(donde):
90
+ return []
91
+ salida = _corre(['lsof', '-t', '+D', donde])
92
+ return sorted({int(p) for p in salida.split() if p.strip().isdigit()}
93
+ - {os.getpid(), os.getppid()})
94
+
95
+
96
+ def mata(pids):
97
+ """TERM first, KILL for whatever ignores it. Returns how many actually died."""
98
+ if not pids:
99
+ return 0
100
+ for pid in pids:
101
+ try:
102
+ os.kill(pid, signal.SIGTERM)
103
+ except (ProcessLookupError, PermissionError):
104
+ pass
105
+ time.sleep(1.2)
106
+ muertos = 0
107
+ for pid in pids:
108
+ try:
109
+ os.kill(pid, 0)
110
+ os.kill(pid, signal.SIGKILL)
111
+ except ProcessLookupError:
112
+ muertos += 1
113
+ except PermissionError:
114
+ pass
115
+ return muertos
116
+
117
+
118
+ def _argumentos():
119
+ ap = argparse.ArgumentParser(add_help=False)
120
+ ap.add_argument('ciudad', nargs='?', default='',
121
+ help='close only this city (name or path); empty closes all')
122
+ ap.add_argument('--dry-run', action='store_true')
123
+ ap.add_argument('-h', '--help', action='store_true')
124
+ return ap.parse_args()
125
+
126
+
127
+ def _objetivo(nombre):
128
+ if not nombre:
129
+ return ''
130
+ solo = cities.resuelve(nombre)
131
+ if solo:
132
+ print(f' Only the city at {solo} — the rest keep working.')
133
+ return solo
134
+ conocidas = ', '.join(c['nombre'] for c in cities.lista()) or 'none'
135
+ raise ValueError(f'No city called {nombre!r}. Known here: {conocidas}.')
136
+
137
+
138
+ def _recursos(solo):
139
+ ciudades = [solo] if solo else [c['ruta'] for c in cities.lista()]
140
+ procesos = [(ciudad, proceso) for ciudad in ciudades
141
+ for proceso in runtime_processes.procesos(ciudad)]
142
+ return {
143
+ 'sesiones': sesiones_de_la_ciudad(solo),
144
+ # The hall covers all cities, so a scoped exit leaves it alive.
145
+ 'webs': [] if solo else servidores(),
146
+ 'mapas': mapas(solo),
147
+ 'ciudades': ciudades,
148
+ 'bus': procesos,
149
+ }
150
+
151
+
152
+ def _vacio(recursos):
153
+ return not any(recursos[k] for k in ('sesiones', 'webs', 'mapas', 'bus'))
154
+
155
+
156
+ def _describe(recursos, dry_run):
157
+ verbo = 'Would close' if dry_run else 'Closing'
158
+ for sesion_ in recursos['sesiones']:
159
+ ventanas = _corre(['tmux', 'list-windows', '-t', sesion_]).count('\n')
160
+ print(f' {verbo}: tmux session `{sesion_}` — {ventanas} windows, '
161
+ f'every agent inside included')
162
+ webs = recursos['webs']
163
+ if webs:
164
+ print(f' {verbo}: the town hall server ({len(webs)} process'
165
+ f'{"es" if len(webs) > 1 else ""})')
166
+ dibujos = recursos['mapas']
167
+ if dibujos:
168
+ print(f' {verbo}: the map ({len(dibujos)} process'
169
+ f'{"es" if len(dibujos) > 1 else ""} holding the local database)')
170
+ for _, proceso in recursos['bus']:
171
+ print(f' {verbo}: {proceso["label"]} (pid {proceso["pid"]})')
172
+
173
+
174
+ def _cierra_sesiones(sesiones):
175
+ """Close our current tmux session last so this command can finish."""
176
+ propia = os.environ.get('TMUX', '')
177
+ actual = _corre(['tmux', 'display-message', '-p', '#{session_name}']).strip()
178
+ ultimas = []
179
+ for sesion_ in sesiones:
180
+ if propia and actual == sesion_:
181
+ ultimas.append(sesion_)
182
+ else:
183
+ subprocess.run(['tmux', 'kill-session', '-t', sesion_], capture_output=True)
184
+ return ultimas
185
+
186
+
187
+ def _cierra(recursos):
188
+ # Sessions first: SIGHUP lets every runtime exit on its own terms.
189
+ ultimas = _cierra_sesiones(recursos['sesiones'])
190
+ mata(recursos['webs'])
191
+ mata(recursos['mapas'])
192
+ mata([proceso['pid'] for _, proceso in recursos['bus']])
193
+ # A gateway can notice the hub dying in the narrow interval before tmux's
194
+ # SIGHUP reaches it and recreate a hub. Re-scan exact --data argv after the
195
+ # session is gone; this also catches old hubs whose marker was overwritten.
196
+ for _ in range(3):
197
+ restantes = [proceso['pid'] for ciudad in recursos['ciudades']
198
+ for proceso in runtime_processes.procesos(ciudad)]
199
+ if not restantes:
200
+ break
201
+ mata(sorted(set(restantes)))
202
+ for ciudad in recursos['ciudades']:
203
+ runtime_processes.limpia_marcas(ciudad)
204
+ for sesion_ in ultimas:
205
+ subprocess.run(['tmux', 'kill-session', '-t', sesion_], capture_output=True)
206
+
207
+
208
+ def main():
209
+ a = _argumentos()
210
+ if a.help:
211
+ print(__doc__)
212
+ return 0
213
+
214
+ try:
215
+ solo = _objetivo(a.ciudad)
216
+ except ValueError as error:
217
+ print(f' {error}', file=sys.stderr)
218
+ return 1
219
+ recursos = _recursos(solo)
220
+ if _vacio(recursos):
221
+ print(' Nothing of the city is running. The machine is yours.')
222
+ return 0
223
+ _describe(recursos, a.dry_run)
224
+ if a.dry_run:
225
+ return 0
226
+ _cierra(recursos)
227
+ print(' Done. `agents-city seat` brings it all back.')
228
+ return 0
229
+
230
+
231
+ if __name__ == '__main__':
232
+ sys.exit(main())
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env python3
2
+ """A face for every agent, drawn from its name and nothing else.
3
+
4
+ The Hall showed rows of identical dots: you could not tell at a glance who was
5
+ who. This gives each agent a small, distinct, deterministic avatar — same name,
6
+ same face, forever — as a self-contained inline SVG. No network, no library, no
7
+ external asset, so it drops straight into the Hall under its strict CSP.
8
+
9
+ The design is an identicon: the name is hashed once, and every visual choice —
10
+ two palette colours and a symmetric 5×5 cell pattern — is read out of that hash.
11
+ Symmetry (the left half mirrored to the right) is what makes random noise read
12
+ as a face-like glyph rather than static. A ring of `kind` tints the border so a
13
+ code agent, a knowledge agent and a coordinator are also distinguishable by hue.
14
+ """
15
+
16
+ import hashlib
17
+
18
+ #: Border tints by agent kind. The kind vocabulary itself lives once in
19
+ #: `workspace.CLASES`; each kind gets a tint here, and an unknown or newly-added
20
+ #: kind degrades to the default tint (see `svg`) rather than failing — no
21
+ #: import-time assert, which `python -O` would strip and which would otherwise
22
+ #: crash every consumer on a partial change.
23
+ TINTE_CLASE = {
24
+ 'code': '#3b82f6', # blue
25
+ 'knowledge': '#8b5cf6', # violet
26
+ 'coordinator': '#f59e0b', # amber
27
+ }
28
+ _TINTE_DEFECTO = '#64748b' # slate
29
+
30
+ _CELDAS = 5 # a 5×5 grid, left half mirrored
31
+ _LADO = 90 # drawable area, leaving a border
32
+ _MARGEN = 15
33
+
34
+
35
+ def _digest(nombre):
36
+ return hashlib.sha256(nombre.encode('utf-8')).digest()
37
+
38
+
39
+ def _color(digest, desfase):
40
+ """A pleasant, saturated colour from three hash bytes, kept mid-bright."""
41
+ r = 70 + (digest[desfase] % 150)
42
+ g = 70 + (digest[desfase + 1] % 150)
43
+ b = 70 + (digest[desfase + 2] % 150)
44
+ return f'#{r:02x}{g:02x}{b:02x}'
45
+
46
+
47
+ def _celdas_encendidas(digest):
48
+ """Which of the 15 left+centre cells are on, mirrored to 25. Deterministic."""
49
+ encendidas = set()
50
+ columnas = (_CELDAS + 1) // 2 # 3 columns: mirror the first two
51
+ for fila in range(_CELDAS):
52
+ for col in range(columnas):
53
+ # One bit per cell, walked through the hash so the pattern is stable.
54
+ bit = digest[(fila * columnas + col) % len(digest)]
55
+ if bit & 1:
56
+ encendidas.add((fila, col))
57
+ encendidas.add((fila, _CELDAS - 1 - col)) # mirror
58
+ return encendidas
59
+
60
+
61
+ #: Thematic pictograms, every one an original couple of strokes in the 120×120
62
+ #: viewbox. The point is resemblance: the pharmacy agent LOOKS like a pharmacy.
63
+ #: Each glyph is drawn with stroke=currentColor semantics via {t} (the agent's
64
+ #: hash tint), so the identicon promise — same inputs, same face — holds.
65
+ _MOTIVOS = {
66
+ 'medico': ('<path d="M52 34h16v18h18v16H68v18H52V68H34V52h18z" '
67
+ 'fill="{t}"/>'),
68
+ 'matraz': ('<path d="M52 32h16M56 32v18l20 32a6 6 0 0 1-5 9H49a6 6 0 0 1-5-9l20-32V32" '
69
+ 'fill="none" stroke="{t}" stroke-width="6" stroke-linejoin="round"/>'
70
+ '<circle cx="60" cy="74" r="5" fill="{t}"/>'),
71
+ 'pildora': ('<rect x="30" y="48" width="60" height="24" rx="12" fill="none" '
72
+ 'stroke="{t}" stroke-width="6"/><path d="M60 48v24" stroke="{t}" '
73
+ 'stroke-width="6"/><path d="M30 60h30" stroke="{t}" stroke-width="6" '
74
+ 'opacity=".35"/>'),
75
+ 'libro': ('<path d="M60 40c-7-6-18-6-26-3v46c8-3 19-3 26 3 7-6 18-6 26-3V37c-8-3-19-3-26 3z" '
76
+ 'fill="none" stroke="{t}" stroke-width="6" stroke-linejoin="round"/>'
77
+ '<path d="M60 40v46" stroke="{t}" stroke-width="5"/>'),
78
+ 'carpeta': ('<path d="M32 44a4 4 0 0 1 4-4h16l8 8h24a4 4 0 0 1 4 4v28a4 4 0 0 1-4 4H36a4 4 0 '
79
+ '0 1-4-4z" fill="none" stroke="{t}" stroke-width="6" stroke-linejoin="round"/>'),
80
+ 'calendario': ('<rect x="32" y="38" width="56" height="46" rx="6" fill="none" stroke="{t}" '
81
+ 'stroke-width="6"/><path d="M32 52h56M44 32v12M76 32v12" stroke="{t}" '
82
+ 'stroke-width="6"/><circle cx="48" cy="66" r="4" fill="{t}"/>'
83
+ '<circle cx="72" cy="66" r="4" fill="{t}"/>'),
84
+ 'balanza': ('<path d="M60 32v52M40 84h40M38 44h44" stroke="{t}" stroke-width="6" '
85
+ 'stroke-linecap="round"/><path d="M38 44l-10 20a12 8 0 0 0 20 0zM82 44l-10 20a12 '
86
+ '8 0 0 0 20 0z" fill="none" stroke="{t}" stroke-width="5" '
87
+ 'stroke-linejoin="round"/>'),
88
+ 'pluma': ('<path d="M78 34c-18 2-32 14-38 32l-6 20 20-6c18-6 30-20 32-38z" fill="none" '
89
+ 'stroke="{t}" stroke-width="6" stroke-linejoin="round"/>'
90
+ '<path d="M40 80l26-26" stroke="{t}" stroke-width="5"/>'),
91
+ 'moneda': ('<circle cx="60" cy="60" r="26" fill="none" stroke="{t}" stroke-width="6"/>'
92
+ '<path d="M60 46v28M52 52h12a7 7 0 0 1 0 14H52" fill="none" stroke="{t}" '
93
+ 'stroke-width="5"/>'),
94
+ 'escudo': ('<path d="M60 32l26 10v18c0 16-11 26-26 30-15-4-26-14-26-30V42z" fill="none" '
95
+ 'stroke="{t}" stroke-width="6" stroke-linejoin="round"/>'
96
+ '<path d="M48 60l8 8 16-16" fill="none" stroke="{t}" stroke-width="6" '
97
+ 'stroke-linecap="round" stroke-linejoin="round"/>'),
98
+ 'codigo': ('<path d="M46 42L28 60l18 18M74 42l18 18-18 18M66 36L54 84" fill="none" '
99
+ 'stroke="{t}" stroke-width="6" stroke-linecap="round" '
100
+ 'stroke-linejoin="round"/>'),
101
+ 'megafono': ('<path d="M34 54v12l34 14V40zM68 40l16-8v56l-16-8" fill="none" stroke="{t}" '
102
+ 'stroke-width="6" stroke-linejoin="round"/>'
103
+ '<path d="M40 68v14" stroke="{t}" stroke-width="6" stroke-linecap="round"/>'),
104
+ 'datos': ('<ellipse cx="60" cy="40" rx="24" ry="9" fill="none" stroke="{t}" '
105
+ 'stroke-width="6"/><path d="M36 40v40c0 5 11 9 24 9s24-4 24-9V40M36 60c0 5 11 9 '
106
+ '24 9s24-4 24-9" fill="none" stroke="{t}" stroke-width="6"/>'),
107
+ 'bandera': ('<path d="M40 88V32M40 36h38l-8 10 8 10H40" fill="none" stroke="{t}" '
108
+ 'stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>'),
109
+ 'brujula': ('<circle cx="60" cy="60" r="27" fill="none" stroke="{t}" stroke-width="6"/>'
110
+ '<path d="M72 48L64 66l-18 8 8-18z" fill="{t}"/>'),
111
+ }
112
+
113
+ #: Keyword → motif. Matched against the agent's operating role and its name,
114
+ #: in Spanish and English, most specific first. A miss falls to the kind.
115
+ _TEMAS = (
116
+ (('urgenc', 'triaje', 'triag', 'medic', 'clinic', 'salud', 'health', 'doctor'), 'medico'),
117
+ (('laborat', 'analisis', 'analys', 'quimic', 'lab'), 'matraz'),
118
+ (('farmac', 'receta', 'pharma', 'pill'), 'pildora'),
119
+ (('legal', 'litig', 'plazo', 'jurid', 'abogad', 'law'), 'balanza'),
120
+ (('contrat', 'redacc', 'escrit', 'writ', 'editor'), 'pluma'),
121
+ (('fiscal', 'impuesto', 'finan', 'contab', 'cfo', 'tax', 'coin'), 'moneda'),
122
+ (('segur', 'security', 'auth'), 'escudo'),
123
+ (('seo', 'market', 'brand', 'growth-mkt', 'social'), 'megafono'),
124
+ (('data', 'etl', 'telemetr', 'dbt', 'sql', 'analytic', 'metric'), 'datos'),
125
+ (('cita', 'agenda', 'recepcion', 'calendar', 'admision', 'booking'), 'calendario'),
126
+ (('historia', 'archivo', 'expedient', 'docs', 'handbook', 'wiki', 'knowledge'), 'libro'),
127
+ (('product', 'po', 'ux', 'design'), 'brujula'),
128
+ (('chair', 'seat', 'coordina', 'direcc'), 'bandera'),
129
+ (('dev', 'engine', 'core', 'api', 'backend', 'front', 'code', 'infra'), 'codigo'),
130
+ )
131
+
132
+ #: When neither role nor name says anything, the kind still says something.
133
+ _MOTIVO_POR_CLASE = {'code': 'codigo', 'knowledge': 'libro', 'coordinator': 'bandera'}
134
+
135
+
136
+ def _motivo(nombre, clase, rol):
137
+ """The glyph this agent deserves: by role first, then name, then kind."""
138
+ for texto in (str(rol or '').lower(), str(nombre or '').lower()):
139
+ if not texto:
140
+ continue
141
+ for claves, motivo in _TEMAS:
142
+ if any(clave in texto for clave in claves):
143
+ return motivo
144
+ return _MOTIVO_POR_CLASE.get(str(clase or '').lower(), 'codigo')
145
+
146
+
147
+ def svg(nombre, clase='code', tamano=64, semilla='', rol=''):
148
+ """A self-contained SVG string for one agent. Pure and deterministic.
149
+
150
+ Thematic on purpose: the face carries a pictogram chosen from the agent's
151
+ role, name or kind — the pharmacy agent LOOKS like a pharmacy — over the
152
+ name-hashed tint and a faint identicon texture that keeps two agents with
153
+ the same trade tellable apart. `semilla` rerolls colours and texture while
154
+ keeping the motif: a new look, the same identity. Empty seed is the classic.
155
+ """
156
+ nombre = str(nombre or 'agent')
157
+ digest = _digest(nombre + (f'#{semilla}' if semilla else ''))
158
+ fondo = '#0f172a'
159
+ tinta = _color(digest, 0)
160
+ textura = _color(digest, 3)
161
+ borde = TINTE_CLASE.get(str(clase or '').lower(), _TINTE_DEFECTO)
162
+ paso = _LADO / _CELDAS
163
+ rects = []
164
+ for (fila, col) in sorted(_celdas_encendidas(digest)):
165
+ x = _MARGEN + col * paso
166
+ y = _MARGEN + fila * paso
167
+ rects.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="{paso:.1f}" '
168
+ f'height="{paso:.1f}" fill="{textura}" opacity="0.16"/>')
169
+ glifo = _MOTIVOS[_motivo(nombre, clase, rol)].replace('{t}', tinta)
170
+ return (
171
+ f'<svg xmlns="http://www.w3.org/2000/svg" width="{tamano}" height="{tamano}" '
172
+ f'viewBox="0 0 120 120" role="img" aria-label="{_escapa(nombre)}">'
173
+ f'<rect width="120" height="120" rx="18" fill="{fondo}"/>'
174
+ f'<rect x="4" y="4" width="112" height="112" rx="15" fill="none" '
175
+ f'stroke="{borde}" stroke-width="6"/>'
176
+ f'{"".join(rects)}{glifo}</svg>'
177
+ )
178
+
179
+
180
+ def _escapa(texto):
181
+ return (texto.replace('&', '&amp;').replace('<', '&lt;')
182
+ .replace('>', '&gt;').replace('"', '&quot;'))
183
+
184
+
185
+ def data_uri(nombre, clase='code', tamano=64, semilla='', rol=''):
186
+ """The avatar as a `data:` URI, ready for an <img src> in the Hall."""
187
+ import base64
188
+ crudo = svg(nombre, clase=clase, tamano=tamano, semilla=semilla, rol=rol).encode('utf-8')
189
+ return 'data:image/svg+xml;base64,' + base64.b64encode(crudo).decode('ascii')
190
+
191
+
192
+ def main():
193
+ import argparse
194
+ import sys
195
+
196
+ p = argparse.ArgumentParser(description='A deterministic SVG avatar for an agent name.')
197
+ p.add_argument('nombre')
198
+ p.add_argument('--kind', default='code', choices=list(TINTE_CLASE))
199
+ p.add_argument('--size', type=int, default=64)
200
+ p.add_argument('--data-uri', action='store_true')
201
+ args = p.parse_args()
202
+ if args.data_uri:
203
+ sys.stdout.write(data_uri(args.nombre, clase=args.kind, tamano=args.size) + '\n')
204
+ else:
205
+ sys.stdout.write(svg(args.nombre, clase=args.kind, tamano=args.size) + '\n')
206
+ return 0
207
+
208
+
209
+ if __name__ == '__main__':
210
+ import sys
211
+ sys.exit(main())