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,252 @@
1
+ #!/usr/bin/env python3
2
+ """The cage: one macOS seatbelt profile per repo window.
3
+
4
+ The yolo flag stays — a committee cannot work if every bus command needs a
5
+ human. What changes is what the kernel lets the window touch. An agent inside
6
+ the cage is never asked anything: writes land only in its own repo and the
7
+ runtime state it legitimately owns, and the files that turn a prompt injection
8
+ into a credential theft (`~/.ssh`, `~/.git-credentials`, cloud configs, the
9
+ `gh` token) simply do not exist for it.
10
+
11
+ Seatbelt semantics, verified on a real machine before this was written:
12
+ the LAST matching rule wins, children and grandchildren inherit the profile,
13
+ and the network stays open unless denied. So the profile reads top to bottom
14
+ as: allow everything, forbid all writes, re-allow the working set, and finally
15
+ seal the secrets — reads and writes both.
16
+
17
+ Honest limits, documented rather than hidden: credentials held in the macOS
18
+ keychain travel over IPC, not file reads, so a jailed `git push` still works
19
+ when the owner keeps GitHub credentials there; outbound network stays open;
20
+ and every window still runs as the same OS user. The cage narrows what a
21
+ compromised window can steal — it is not hostile-process isolation.
22
+ """
23
+
24
+ import argparse
25
+ import os
26
+ import re
27
+ import shutil
28
+ import sys
29
+
30
+ import rutas
31
+
32
+ # Read+write denied inside the cage: directories that exist to hold credentials.
33
+ SECRETOS_DIR = (
34
+ '.ssh', '.aws', '.kube', '.gnupg',
35
+ os.path.join('.config', 'gcloud'),
36
+ os.path.join('.config', 'gh'),
37
+ os.path.join('.docker'),
38
+ )
39
+ # Single files with the same job. `~/.npmrc` is deliberately NOT here: denying
40
+ # it breaks every `npm install` for owners who configure a registry, and a
41
+ # broken product protects nobody. Owners who keep tokens there add it through
42
+ # CITY_CAGE_DENY.
43
+ SECRETOS_FICHERO = ('.git-credentials', '.netrc', '.pgpass')
44
+
45
+ # Writable beyond the repo: the runtime state and build caches a working agent
46
+ # legitimately owns. Everything here is cache or agent state, never a secret —
47
+ # with the exceptions sealed again further down.
48
+ ESCRITURA_CASA = (
49
+ '.claude', '.agents-city', '.codex', '.opencode', '.kimi',
50
+ '.npm', '.cache', '.cargo', '.rustup', '.gradle', '.m2',
51
+ '.pnpm-store', '.bun',
52
+ os.path.join('Library', 'Caches'),
53
+ os.path.join('Library', 'Logs'),
54
+ os.path.join('Library', 'pnpm'),
55
+ os.path.join('Library', 'Developer'),
56
+ )
57
+ ESCRITURA_SISTEMA = ('/dev', '/private/tmp', '/private/var/tmp', '/private/var/folders')
58
+
59
+
60
+ def _sbpl(ruta):
61
+ """Quote one path for SBPL: backslashes and double quotes escaped."""
62
+ return '"' + ruta.replace('\\', '\\\\').replace('"', '\\"') + '"'
63
+
64
+
65
+ def agents_home(casa):
66
+ """The Agents City state root, honouring AGENTS_CITY_HOME like the broker.
67
+
68
+ The broker writes its token store under this root (`broker.estado_de`), so
69
+ the cage must seal the *same* root it uses — not a hardcoded `~/.agents-city`
70
+ that silently misses the broker dir when the owner relocated the home.
71
+ """
72
+ hogar = os.environ.get('AGENTS_CITY_HOME') or os.path.join(casa, '.agents-city')
73
+ return rutas.canonicaliza(hogar)
74
+
75
+
76
+ def sellados(casa):
77
+ """The canonical set of read+write-sealed roots.
78
+
79
+ The single source of truth for "must never be reachable": the deny lines
80
+ emit it, and the write-allow filter refuses any owner path that re-enters
81
+ it. Extra owner denies (CITY_CAGE_DENY) join it, so an owner can widen the
82
+ seal but the allow-list can never outrun it.
83
+ """
84
+ ac = agents_home(casa)
85
+ extra = [p for p in os.environ.get('CITY_CAGE_DENY', '').split(':') if p]
86
+ raices = [os.path.join(casa, d) for d in SECRETOS_DIR]
87
+ raices += [os.path.join(casa, f) for f in SECRETOS_FICHERO]
88
+ raices += [os.path.expanduser(p) for p in extra]
89
+ raices.append(os.path.join(ac, '.runtime', 'broker'))
90
+ raices.append(os.path.join(casa, '.cargo', 'credentials'))
91
+ raices.append(os.path.join(casa, '.cargo', 'credentials.toml'))
92
+ return [rutas.canonicaliza(r) for r in raices]
93
+
94
+
95
+ def _permitidas_escritura(repo, casa, bloqueados, extra_escritura=()):
96
+ """Canonical write-allow roots, given the already-computed sealed set.
97
+
98
+ Covering roots are intentionally kept: the sealed-secret block is emitted
99
+ last and, under seatbelt's last-match-wins rule, re-denies any secret that
100
+ a broad writable ancestor would otherwise expose. So the ordering — not a
101
+ filter here — is the guarantee, and `bin/test-cage.py` proves it against the
102
+ live kernel. What this pass does add is a loud drop of an owner extra that
103
+ resolves strictly *inside* a sealed root: that write is already dead under
104
+ the final deny, so honouring it silently would only mislead. The relocated
105
+ state home is added explicitly so a custom AGENTS_CITY_HOME stays writable.
106
+
107
+ `extra_escritura` is where the agent-first model plugs in: the resolved
108
+ targets of the agent's workspace mounts (symlinks already followed to their
109
+ real path). They pass through the same sealed-root check as any owner extra.
110
+ """
111
+ extra = [os.path.expanduser(p)
112
+ for p in os.environ.get('CITY_CAGE_ALLOW_WRITE', '').split(':') if p]
113
+ extra += list(extra_escritura)
114
+ fijas = [repo, agents_home(casa), *ESCRITURA_SISTEMA]
115
+ fijas += [os.path.join(casa, p) for p in ESCRITURA_CASA]
116
+ aceptadas = [rutas.canonicaliza(c) for c in fijas]
117
+ for c in extra:
118
+ if any(rutas.dentro_de(c, raiz) for raiz in bloqueados):
119
+ sys.stderr.write(f'cage: ignoring write-allow {c}: inside a sealed root\n')
120
+ continue
121
+ aceptadas.append(rutas.canonicaliza(c))
122
+ return aceptadas
123
+
124
+
125
+ def _lineas_permite_escritura(repo, casa, bloqueados, extra_escritura=()):
126
+ permitidas = _permitidas_escritura(repo, casa, bloqueados, extra_escritura)
127
+ lineas = [f' (subpath {_sbpl(r)})' for r in permitidas]
128
+ # Claude keeps `~/.claude.json` (and its backups) at the HOME root.
129
+ lineas.append(f' (regex #"^{re.escape(os.path.join(casa, ".claude.json"))}")')
130
+ return lineas
131
+
132
+
133
+ def _lineas_secretos(casa, bloqueados):
134
+ lineas = []
135
+ for raiz in bloqueados:
136
+ # A file target uses `literal`; a directory (or a not-yet-created dir)
137
+ # uses `subpath` so everything beneath it is sealed too.
138
+ if os.path.isfile(raiz):
139
+ lineas.append(f' (literal {_sbpl(raiz)})')
140
+ else:
141
+ lineas.append(f' (subpath {_sbpl(raiz)})')
142
+ # Remote road tokens configured for the seat: repo windows must never read
143
+ # them, even though the rest of `~/.claude` stays open for the runtime.
144
+ canal = re.escape(os.path.join(casa, '.claude', 'channels'))
145
+ lineas.append(f' (regex #"^{canal}/[^/]+/\\.env$")')
146
+ return lineas
147
+
148
+
149
+ def perfil(repo, casa=None, fichero_token=None, extra_escritura=()):
150
+ """The SBPL profile for one window, as text. Pure: no filesystem writes.
151
+
152
+ `repo` is the window's working directory (a repo, or an agent workspace).
153
+ `extra_escritura` are additional writable roots — the resolved targets of an
154
+ agent's workspace mounts — sealed-root-checked like any other write-allow.
155
+ """
156
+ casa = rutas.canonicaliza(casa or '~')
157
+ repo = rutas.canonicaliza(repo)
158
+ if not os.path.isdir(repo):
159
+ raise ValueError(f'the cage needs an existing working directory, got: {repo}')
160
+ # Compute the sealed set once and thread it through every helper below.
161
+ bloqueados = sellados(casa)
162
+ motivo = rutas.motivo_bloqueo(repo, bloqueados)
163
+ if motivo:
164
+ raise ValueError(f'the working directory is unsafe to cage: {motivo}')
165
+ partes = [
166
+ '(version 1)',
167
+ '; Agents City cage — generated, one per window. Do not edit.',
168
+ '(allow default)',
169
+ '(deny file-write*)',
170
+ '(allow file-write*',
171
+ *_lineas_permite_escritura(repo, casa, bloqueados, extra_escritura),
172
+ ')',
173
+ '(deny file-read* file-write*',
174
+ *_lineas_secretos(casa, bloqueados),
175
+ ')',
176
+ ]
177
+ if fichero_token:
178
+ # This window's own broker token, and only this window's: the broker
179
+ # directory deny above stays in force for every other file in it.
180
+ partes.append(f'(allow file-read* (literal {_sbpl(rutas.canonicaliza(fichero_token))}))')
181
+ return '\n'.join(partes) + '\n'
182
+
183
+
184
+ def disponible():
185
+ """Whether this machine can cage a window at all."""
186
+ if os.environ.get('CITY_CAGE', '1') == '0':
187
+ return False
188
+ return sys.platform == 'darwin' and shutil.which('sandbox-exec') is not None
189
+
190
+
191
+ def escribe_perfil(repo, ventana, casa=None, fichero_token=None, extra_escritura=()):
192
+ """Write the profile under the runtime dir and return its path."""
193
+ casa_real = os.path.realpath(casa or os.path.expanduser('~'))
194
+ directorio = os.path.join(casa_real, '.agents-city', '.runtime', 'cage')
195
+ os.makedirs(directorio, mode=0o700, exist_ok=True)
196
+ segmento = re.sub(r'[^a-zA-Z0-9_-]', '-', ventana) or 'window'
197
+ ruta = os.path.join(directorio, f'{segmento}.sb')
198
+ contenido = perfil(repo, casa=casa_real, fichero_token=fichero_token,
199
+ extra_escritura=extra_escritura)
200
+ with open(ruta, 'w', encoding='utf-8') as f:
201
+ f.write(contenido)
202
+ os.chmod(ruta, 0o600)
203
+ return ruta
204
+
205
+
206
+ def linea(repo, ventana, casa=None, fichero_token=None, extra_escritura=()):
207
+ """The launch prefix for one window: `sandbox-exec -f <profile> `, or ''.
208
+
209
+ Empty means "launch uncaged": not macOS, no sandbox-exec, or the owner set
210
+ CITY_CAGE=0. The caller prepends the result verbatim, so the degraded path
211
+ is exactly the behaviour the product always had.
212
+ """
213
+ if not disponible():
214
+ return ''
215
+ ruta = escribe_perfil(repo, ventana, casa=casa, fichero_token=fichero_token,
216
+ extra_escritura=extra_escritura)
217
+ return f'sandbox-exec -f {ruta} '
218
+
219
+
220
+ def main():
221
+ p = argparse.ArgumentParser(description='One seatbelt cage per window.')
222
+ p.add_argument('orden', choices=['line', 'profile', 'check'])
223
+ p.add_argument('--repo', help='the working directory this window owns')
224
+ p.add_argument('--window', default='window', help='window name, used to name the profile')
225
+ p.add_argument('--home', help='HOME override (tests only)')
226
+ p.add_argument('--token-file', help="this window's broker token file, allowed read-only")
227
+ p.add_argument('--mounts', default='',
228
+ help='colon-separated resolved mount targets to also make writable')
229
+ args = p.parse_args()
230
+ if args.orden == 'check':
231
+ print('cage available' if disponible() else 'cage unavailable on this machine')
232
+ return 0 if disponible() else 1
233
+ if not args.repo:
234
+ print('--repo is required', file=sys.stderr)
235
+ return 2
236
+ extra = tuple(m for m in args.mounts.split(':') if m)
237
+ try:
238
+ if args.orden == 'profile':
239
+ sys.stdout.write(perfil(args.repo, casa=args.home, fichero_token=args.token_file,
240
+ extra_escritura=extra))
241
+ else:
242
+ sys.stdout.write(
243
+ linea(args.repo, args.window, casa=args.home, fichero_token=args.token_file,
244
+ extra_escritura=extra))
245
+ except (OSError, ValueError) as e:
246
+ print(f'{e}', file=sys.stderr)
247
+ return 1
248
+ return 0
249
+
250
+
251
+ if __name__ == '__main__':
252
+ sys.exit(main())
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ """Live, read-only recognition of skills available to a city's repo agents.
3
+
4
+ Agents City never installs, copies, enables or edits a skill. The runtime in a
5
+ repo remains the source of truth and decides whether a skill applies to a task.
6
+ This module only discovers conventional repo-local ``SKILL.md`` files so a seat
7
+ can see which agent is likely to know how to help.
8
+ """
9
+
10
+ import os
11
+ import re
12
+ import subprocess
13
+ import sys
14
+
15
+ import card
16
+ import cities
17
+ import workspace
18
+
19
+
20
+ RAICES = (".claude/skills", ".codex/skills", ".agents/skills", "skills")
21
+
22
+
23
+ def _campo(texto, clave):
24
+ if not texto.startswith("---"):
25
+ return ""
26
+ partes = texto.split("---", 2)
27
+ if len(partes) < 3:
28
+ return ""
29
+ m = re.search(rf"^{re.escape(clave)}:[ \t]*(.+)$", partes[1], re.M)
30
+ return m.group(1).strip().strip("\"'") if m else ""
31
+
32
+
33
+ def descubre_repo(ruta_repo):
34
+ """Skills declared by this repo, with no cache and no writes."""
35
+ fuera, vistos = [], set()
36
+ raiz_manifest = os.path.join(ruta_repo, "SKILL.md")
37
+ if os.path.isfile(raiz_manifest):
38
+ try:
39
+ texto = open(raiz_manifest, encoding="utf-8").read()
40
+ except OSError:
41
+ texto = ""
42
+ if texto:
43
+ real = os.path.realpath(raiz_manifest)
44
+ vistos.add(real)
45
+ fuera.append(
46
+ {
47
+ "name": _campo(texto, "name") or os.path.basename(ruta_repo),
48
+ "description": _campo(texto, "description"),
49
+ "manifest": real,
50
+ "provider": "repo",
51
+ }
52
+ )
53
+ for relativa in RAICES:
54
+ base = os.path.join(ruta_repo, relativa)
55
+ if not os.path.isdir(base):
56
+ continue
57
+ for nombre in sorted(os.listdir(base)):
58
+ manifiesto = os.path.join(base, nombre, "SKILL.md")
59
+ if not os.path.isfile(manifiesto):
60
+ continue
61
+ real = os.path.realpath(manifiesto)
62
+ if real in vistos:
63
+ continue
64
+ vistos.add(real)
65
+ try:
66
+ texto = open(manifiesto, encoding="utf-8").read()
67
+ except OSError:
68
+ continue
69
+ fuera.append(
70
+ {
71
+ "name": _campo(texto, "name") or nombre,
72
+ "description": _campo(texto, "description"),
73
+ "manifest": real,
74
+ "provider": relativa.split("/", 1)[0].lstrip(".") or "repo",
75
+ }
76
+ )
77
+ return fuera
78
+
79
+
80
+ def _indice_repos():
81
+ guion = os.path.join(os.path.dirname(__file__), "find-repos.sh")
82
+ try:
83
+ salida = subprocess.run([guion], capture_output=True, text=True, timeout=300).stdout
84
+ except (OSError, subprocess.TimeoutExpired):
85
+ return {}
86
+ fuera = {}
87
+ for linea in salida.splitlines():
88
+ if "\t" in linea:
89
+ nombre, ruta = linea.split("\t", 1)
90
+ fuera[nombre.strip()] = ruta.strip()
91
+ return fuera
92
+
93
+
94
+ def ficha_de_ciudad(datos):
95
+ owner = cities.lee_clave(datos, "owner")
96
+ preferida = os.path.join(datos, f"{owner}.md") if owner else ""
97
+ if preferida and os.path.isfile(preferida):
98
+ return preferida
99
+ try:
100
+ fichas = sorted(
101
+ os.path.join(datos, f)
102
+ for f in os.listdir(datos)
103
+ if f.endswith(".md") and card.lee(os.path.join(datos, f)).get("user")
104
+ )
105
+ except OSError:
106
+ fichas = []
107
+ return fichas[0] if fichas else ""
108
+
109
+
110
+ def ruta_de(nombre, rutas=None):
111
+ """One legacy repo's folder on this disk, or ''. The single mechanism both
112
+ full discovery and one-agent lookups share — resolving one path must never
113
+ cost a whole-city skill scan."""
114
+ rutas = _indice_repos() if rutas is None else rutas
115
+ code = os.path.expanduser(os.environ.get("CITY_CODE_DIR", "~/codigo"))
116
+ ruta = rutas.get(nombre, os.path.join(code, nombre))
117
+ return os.path.realpath(ruta) if os.path.isdir(ruta) else ""
118
+
119
+
120
+ def descubre_ciudad(datos, rutas=None):
121
+ """``{agent: {path, role, skills}}`` for the city's support agents.
122
+
123
+ Both card shapes, through the one normaliser (`workspace.agentes`): a
124
+ legacy repo agent looks for its repo on disk exactly as before, and an
125
+ agents-first agent looks in its own workspace and in every mount it
126
+ declares. Before this, an agents-first city honestly full of skills showed
127
+ "Agents & skills 0" — the discovery only knew `repos:`.
128
+ """
129
+ ficha = ficha_de_ciudad(datos)
130
+ if not ficha:
131
+ return {}
132
+ try:
133
+ texto = open(ficha, encoding="utf-8").read()
134
+ except OSError:
135
+ return {}
136
+ try:
137
+ agentes = workspace.agentes(texto, datos)
138
+ except ValueError:
139
+ # A malformed card (two names that slug to one identity) is the card
140
+ # tools' error to raise loudly; read-only discovery just has nothing
141
+ # safe to list for it.
142
+ return {}
143
+ if not agentes:
144
+ return {}
145
+ # The disk index is a find-repos scan: paid lazily, and only when a legacy
146
+ # agent actually needs it — a pure agents-first city never touches it.
147
+ fuera = {}
148
+ for a in agentes:
149
+ if a.legacy:
150
+ rutas = _indice_repos() if rutas is None else rutas
151
+ candidatos = [ruta_de(a.nombre, rutas)]
152
+ else:
153
+ candidatos = [a.workspace] + [os.path.expanduser(m) for m in a.mounts]
154
+ existentes = [c for c in candidatos if c and os.path.isdir(c)]
155
+ skills = []
156
+ vistos = set()
157
+ for c in existentes:
158
+ for s in descubre_repo(c):
159
+ if s["name"] in vistos:
160
+ continue
161
+ vistos.add(s["name"])
162
+ skills.append(s)
163
+ fuera[a.nombre] = {
164
+ "path": os.path.realpath(existentes[0]) if existentes else "",
165
+ "role": a.rol,
166
+ "skills": skills,
167
+ }
168
+ return fuera
169
+
170
+
171
+ def main():
172
+ if len(sys.argv) > 1 and sys.argv[1] in ("-h", "--help", "help"):
173
+ print(
174
+ "usage: agents-city skills [city]\n\n"
175
+ "Read-only discovery of skills already installed in each repo."
176
+ )
177
+ return 0
178
+ usuario = cities.usuario_actual()
179
+ pedida = sys.argv[1] if len(sys.argv) > 1 else ""
180
+ datos = cities.resuelve(pedida, usuario) if pedida else cities.actual(usuario)
181
+ if not datos:
182
+ print(f" No city called {pedida!r}.", file=sys.stderr)
183
+ return 1
184
+ capacidades = descubre_ciudad(datos)
185
+ print(f" Skills visible to {cities.direccion(usuario, datos)}")
186
+ if not capacidades:
187
+ print(" No repo agents in this city.")
188
+ for repo, info in capacidades.items():
189
+ nombres = ", ".join(s["name"] for s in info["skills"]) or "none discovered"
190
+ estado = info["path"] or "repo not found on this machine"
191
+ print(f" {repo} [{info['role']}]: {nombres}\n {estado}")
192
+ print("\n Read-only discovery: Agents City installed and changed nothing.")
193
+ return 0
194
+
195
+
196
+ if __name__ == "__main__":
197
+ sys.exit(main())