agents-city 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (449) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +5 -0
  4. package/README.es.md +2226 -0
  5. package/README.md +2196 -0
  6. package/benchmarks/committee/README.md +39 -0
  7. package/benchmarks/committee/metrics.py +51 -0
  8. package/benchmarks/committee/run.py +64 -0
  9. package/benchmarks/committee/traces/01-single.json +8 -0
  10. package/benchmarks/committee/traces/02-mesh.json +16 -0
  11. package/benchmarks/committee/traces/03-chair.json +15 -0
  12. package/benchmarks/latency/README.md +35 -0
  13. package/benchmarks/latency/fake-claude-cli.mjs +119 -0
  14. package/benchmarks/latency/fake-native-server.mjs +340 -0
  15. package/benchmarks/latency/fake-tui.py +91 -0
  16. package/benchmarks/latency/live.py +380 -0
  17. package/benchmarks/stress/README.md +20 -0
  18. package/benchmarks/stress/run.py +487 -0
  19. package/bin/agents +4 -0
  20. package/bin/agents-city.js +119 -0
  21. package/bin/benchmark +17 -0
  22. package/bin/bus +6 -0
  23. package/bin/cities +3 -0
  24. package/bin/city +86 -0
  25. package/bin/committee +6 -0
  26. package/bin/demo +200 -0
  27. package/bin/doctor +3 -0
  28. package/bin/exit +5 -0
  29. package/bin/hall +73 -0
  30. package/bin/hall.html +498 -0
  31. package/bin/logs +3 -0
  32. package/bin/navegador.mjs +243 -0
  33. package/bin/report.py +14 -0
  34. package/bin/reset +3 -0
  35. package/bin/road +3 -0
  36. package/bin/seat +10 -0
  37. package/bin/serve.py +1579 -0
  38. package/bin/setup.py +753 -0
  39. package/bin/shortcut +3 -0
  40. package/bin/skills +3 -0
  41. package/bin/test +133 -0
  42. package/bin/test-actualiza.py +130 -0
  43. package/bin/test-adapter.py +183 -0
  44. package/bin/test-admision.py +99 -0
  45. package/bin/test-atajos.py +301 -0
  46. package/bin/test-avatar.py +58 -0
  47. package/bin/test-benchmark.py +40 -0
  48. package/bin/test-broker.py +334 -0
  49. package/bin/test-cage.py +374 -0
  50. package/bin/test-card.py +402 -0
  51. package/bin/test-channel.py +379 -0
  52. package/bin/test-cities.py +453 -0
  53. package/bin/test-claude-runtime.py +252 -0
  54. package/bin/test-committee.py +340 -0
  55. package/bin/test-contracts.py +551 -0
  56. package/bin/test-crecimiento.py +164 -0
  57. package/bin/test-demo.py +308 -0
  58. package/bin/test-doctor.py +139 -0
  59. package/bin/test-domains.py +151 -0
  60. package/bin/test-evidencia.py +50 -0
  61. package/bin/test-exit.py +128 -0
  62. package/bin/test-hall-protocol.py +93 -0
  63. package/bin/test-launch.py +169 -0
  64. package/bin/test-live-feed.py +508 -0
  65. package/bin/test-navegador.py +140 -0
  66. package/bin/test-pairing.py +99 -0
  67. package/bin/test-parcels.py +283 -0
  68. package/bin/test-runtime-failures.py +252 -0
  69. package/bin/test-runtime-ui.py +488 -0
  70. package/bin/test-runtime.py +268 -0
  71. package/bin/test-rutas.py +80 -0
  72. package/bin/test-seat.py +1598 -0
  73. package/bin/test-security.py +150 -0
  74. package/bin/test-serve.py +1251 -0
  75. package/bin/test-stress.py +64 -0
  76. package/bin/test-widgets.py +143 -0
  77. package/bin/test-workspace.py +251 -0
  78. package/bin/testlib.py +118 -0
  79. package/bin/tokens.py +14 -0
  80. package/bin/update +3 -0
  81. package/bus/scripts/deploy.sh +60 -0
  82. package/bus/scripts/mint-token.sh +68 -0
  83. package/bus/scripts/setup-dev.sh +79 -0
  84. package/bus/scripts/test-channel.ts +8 -0
  85. package/bus/scripts/test-hub.ts +266 -0
  86. package/bus/scripts/test-local.sh +40 -0
  87. package/bus/scripts/test-queue.ts +132 -0
  88. package/bus/worker/package-lock.json +1971 -0
  89. package/bus/worker/package.json +17 -0
  90. package/bus/worker/src/index.ts +485 -0
  91. package/bus/worker/tsconfig.json +13 -0
  92. package/bus/worker/wrangler.toml +32 -0
  93. package/city/oven/README.md +59 -0
  94. package/city/oven/collect.py +43 -0
  95. package/city/oven/oven.html +4 -0
  96. package/city/scripts/history.py +120 -0
  97. package/city/scripts/seed.py +442 -0
  98. package/city/web/assets/modelos/License.txt +28 -0
  99. package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
  100. package/city/web/assets/modelos/building-a.glb +0 -0
  101. package/city/web/assets/modelos/building-b.glb +0 -0
  102. package/city/web/assets/modelos/building-c.glb +0 -0
  103. package/city/web/assets/modelos/building-d.glb +0 -0
  104. package/city/web/assets/modelos/building-e.glb +0 -0
  105. package/city/web/assets/modelos/building-f.glb +0 -0
  106. package/city/web/assets/modelos/building-g.glb +0 -0
  107. package/city/web/assets/modelos/building-h.glb +0 -0
  108. package/city/web/assets/sprites/building-a.png +0 -0
  109. package/city/web/assets/sprites/building-b.png +0 -0
  110. package/city/web/assets/sprites/building-c.png +0 -0
  111. package/city/web/assets/sprites/building-d.png +0 -0
  112. package/city/web/assets/sprites/building-e.png +0 -0
  113. package/city/web/assets/sprites/building-f.png +0 -0
  114. package/city/web/assets/sprites/building-g.png +0 -0
  115. package/city/web/assets/sprites/building-h.png +0 -0
  116. package/city/web/assets/sprites/building-i.png +0 -0
  117. package/city/web/assets/sprites/building-j.png +0 -0
  118. package/city/web/assets/sprites/building-k.png +0 -0
  119. package/city/web/assets/sprites/building-l.png +0 -0
  120. package/city/web/assets/sprites/building-m.png +0 -0
  121. package/city/web/assets/sprites/building-n.png +0 -0
  122. package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
  123. package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
  124. package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
  125. package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
  126. package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
  127. package/city/web/assets/sprites/catalogo.json +47 -0
  128. package/city/web/assets/sprites/detail-awning-wide.png +0 -0
  129. package/city/web/assets/sprites/detail-awning.png +0 -0
  130. package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
  131. package/city/web/assets/sprites/detail-overhang.png +0 -0
  132. package/city/web/assets/sprites/detail-parasol-a.png +0 -0
  133. package/city/web/assets/sprites/detail-parasol-b.png +0 -0
  134. package/city/web/assets/sprites/low-detail-building-a.png +0 -0
  135. package/city/web/assets/sprites/low-detail-building-b.png +0 -0
  136. package/city/web/assets/sprites/low-detail-building-c.png +0 -0
  137. package/city/web/assets/sprites/low-detail-building-d.png +0 -0
  138. package/city/web/assets/sprites/low-detail-building-e.png +0 -0
  139. package/city/web/assets/sprites/low-detail-building-f.png +0 -0
  140. package/city/web/assets/sprites/low-detail-building-g.png +0 -0
  141. package/city/web/assets/sprites/low-detail-building-h.png +0 -0
  142. package/city/web/assets/sprites/low-detail-building-i.png +0 -0
  143. package/city/web/assets/sprites/low-detail-building-j.png +0 -0
  144. package/city/web/assets/sprites/low-detail-building-k.png +0 -0
  145. package/city/web/assets/sprites/low-detail-building-l.png +0 -0
  146. package/city/web/assets/sprites/low-detail-building-m.png +0 -0
  147. package/city/web/assets/sprites/low-detail-building-n.png +0 -0
  148. package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
  149. package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
  150. package/city/web/assets/sprites/medidas.json +248 -0
  151. package/city/web/dist/city.js +939 -0
  152. package/city/web/dist/index.html +294 -0
  153. package/city/web/dist/modelos/License.txt +28 -0
  154. package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
  155. package/city/web/dist/modelos/building-a.glb +0 -0
  156. package/city/web/dist/modelos/building-b.glb +0 -0
  157. package/city/web/dist/modelos/building-c.glb +0 -0
  158. package/city/web/dist/modelos/building-d.glb +0 -0
  159. package/city/web/dist/modelos/building-e.glb +0 -0
  160. package/city/web/dist/modelos/building-f.glb +0 -0
  161. package/city/web/dist/modelos/building-g.glb +0 -0
  162. package/city/web/dist/modelos/building-h.glb +0 -0
  163. package/city/web/dist/sprites/building-a.png +0 -0
  164. package/city/web/dist/sprites/building-b.png +0 -0
  165. package/city/web/dist/sprites/building-c.png +0 -0
  166. package/city/web/dist/sprites/building-d.png +0 -0
  167. package/city/web/dist/sprites/building-e.png +0 -0
  168. package/city/web/dist/sprites/building-f.png +0 -0
  169. package/city/web/dist/sprites/building-g.png +0 -0
  170. package/city/web/dist/sprites/building-h.png +0 -0
  171. package/city/web/dist/sprites/building-i.png +0 -0
  172. package/city/web/dist/sprites/building-j.png +0 -0
  173. package/city/web/dist/sprites/building-k.png +0 -0
  174. package/city/web/dist/sprites/building-l.png +0 -0
  175. package/city/web/dist/sprites/building-m.png +0 -0
  176. package/city/web/dist/sprites/building-n.png +0 -0
  177. package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
  178. package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
  179. package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
  180. package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
  181. package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
  182. package/city/web/dist/sprites/catalogo.json +47 -0
  183. package/city/web/dist/sprites/detail-awning-wide.png +0 -0
  184. package/city/web/dist/sprites/detail-awning.png +0 -0
  185. package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
  186. package/city/web/dist/sprites/detail-overhang.png +0 -0
  187. package/city/web/dist/sprites/detail-parasol-a.png +0 -0
  188. package/city/web/dist/sprites/detail-parasol-b.png +0 -0
  189. package/city/web/dist/sprites/low-detail-building-a.png +0 -0
  190. package/city/web/dist/sprites/low-detail-building-b.png +0 -0
  191. package/city/web/dist/sprites/low-detail-building-c.png +0 -0
  192. package/city/web/dist/sprites/low-detail-building-d.png +0 -0
  193. package/city/web/dist/sprites/low-detail-building-e.png +0 -0
  194. package/city/web/dist/sprites/low-detail-building-f.png +0 -0
  195. package/city/web/dist/sprites/low-detail-building-g.png +0 -0
  196. package/city/web/dist/sprites/low-detail-building-h.png +0 -0
  197. package/city/web/dist/sprites/low-detail-building-i.png +0 -0
  198. package/city/web/dist/sprites/low-detail-building-j.png +0 -0
  199. package/city/web/dist/sprites/low-detail-building-k.png +0 -0
  200. package/city/web/dist/sprites/low-detail-building-l.png +0 -0
  201. package/city/web/dist/sprites/low-detail-building-m.png +0 -0
  202. package/city/web/dist/sprites/low-detail-building-n.png +0 -0
  203. package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
  204. package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
  205. package/city/web/dist/sprites/medidas.json +248 -0
  206. package/city/web/dist-hall/hall.js +1363 -0
  207. package/city/web/index.html +294 -0
  208. package/city/web/package.json +18 -0
  209. package/city/web/sella.py +21 -0
  210. package/city/web/src/activity-actors.ts +47 -0
  211. package/city/web/src/activity.ts +172 -0
  212. package/city/web/src/ayuntamiento.ts +580 -0
  213. package/city/web/src/draw.ts +640 -0
  214. package/city/web/src/game-speech.ts +79 -0
  215. package/city/web/src/hall.ts +1959 -0
  216. package/city/web/src/main.ts +2252 -0
  217. package/city/web/src/oven.ts +124 -0
  218. package/city/web/src/people.ts +244 -0
  219. package/city/web/src/presencia.ts +193 -0
  220. package/city/web/src/puertas.ts +180 -0
  221. package/city/web/src/repo-roles.ts +59 -0
  222. package/city/worker/package.json +16 -0
  223. package/city/worker/schema.sql +117 -0
  224. package/city/worker/src/index.ts +702 -0
  225. package/city/worker/src/square.ts +218 -0
  226. package/city/worker/wrangler.toml +58 -0
  227. package/demo/ada.md +50 -0
  228. package/demo/bruno.md +62 -0
  229. package/demo/camila.md +56 -0
  230. package/demo/city/ada.md +40 -0
  231. package/demo/city/city.yml +8 -0
  232. package/demo/city/parcels.yml +15 -0
  233. package/demo/city/roads.json +4 -0
  234. package/demo/city/units.yml +10 -0
  235. package/demo/clinica/city.yml +8 -0
  236. package/demo/clinica/parcels.yml +15 -0
  237. package/demo/clinica/roads.json +4 -0
  238. package/demo/clinica/units.yml +9 -0
  239. package/demo/clinica/vera.md +46 -0
  240. package/demo/dante.md +54 -0
  241. package/demo/despacho/city.yml +8 -0
  242. package/demo/despacho/marta.md +46 -0
  243. package/demo/despacho/parcels.yml +15 -0
  244. package/demo/despacho/roads.json +4 -0
  245. package/demo/despacho/units.yml +9 -0
  246. package/demo/elsa.md +50 -0
  247. package/demo/farid.md +52 -0
  248. package/demo/greta.md +50 -0
  249. package/demo/hugo.md +49 -0
  250. package/demo/iris.md +49 -0
  251. package/demo/jonas.md +51 -0
  252. package/demo/kira.md +51 -0
  253. package/demo/luca.md +49 -0
  254. package/demo/parcels.yml +92 -0
  255. package/demo/seed.py +20 -0
  256. package/demo/show.py +161 -0
  257. package/demo/stories.py +824 -0
  258. package/demo/units.yml +33 -0
  259. package/docs/agents-first.md +106 -0
  260. package/docs/glossary.md +55 -0
  261. package/docs/map-live-layers.md +73 -0
  262. package/docs/security.md +265 -0
  263. package/docs/self-host.md +151 -0
  264. package/docs/testing.md +205 -0
  265. package/package.json +73 -0
  266. package/plugin/.claude-plugin/plugin.json +62 -0
  267. package/plugin/.mcp.json +15 -0
  268. package/plugin/channel/activity-cli.ts +16 -0
  269. package/plugin/channel/adapter-prompts.ts +113 -0
  270. package/plugin/channel/adapter.js +4292 -0
  271. package/plugin/channel/adapter.ts +133 -0
  272. package/plugin/channel/bus.js +20188 -0
  273. package/plugin/channel/bus.ts +131 -0
  274. package/plugin/channel/city-config.ts +125 -0
  275. package/plugin/channel/claude-channel.ts +58 -0
  276. package/plugin/channel/cli-args.ts +75 -0
  277. package/plugin/channel/client.js +4200 -0
  278. package/plugin/channel/client.ts +38 -0
  279. package/plugin/channel/committee/activity.ts +256 -0
  280. package/plugin/channel/committee/collection.ts +154 -0
  281. package/plugin/channel/committee/decision.ts +175 -0
  282. package/plugin/channel/committee/floor.ts +135 -0
  283. package/plugin/channel/committee/guards.ts +30 -0
  284. package/plugin/channel/committee/history.ts +38 -0
  285. package/plugin/channel/committee/render.ts +102 -0
  286. package/plugin/channel/committee/service.ts +72 -0
  287. package/plugin/channel/committee/storage.ts +92 -0
  288. package/plugin/channel/committee/types.ts +163 -0
  289. package/plugin/channel/committee/view.ts +63 -0
  290. package/plugin/channel/committee-cli.ts +109 -0
  291. package/plugin/channel/delivery-metrics.ts +61 -0
  292. package/plugin/channel/delivery-queue.ts +118 -0
  293. package/plugin/channel/hub/activity-controller.ts +72 -0
  294. package/plugin/channel/hub/activity-feed.ts +169 -0
  295. package/plugin/channel/hub/committee-controller.ts +60 -0
  296. package/plugin/channel/hub/connections.ts +48 -0
  297. package/plugin/channel/hub/diagnostics.ts +67 -0
  298. package/plugin/channel/hub/envelope-validity.ts +109 -0
  299. package/plugin/channel/hub/envelopes.ts +66 -0
  300. package/plugin/channel/hub/lifecycle.ts +91 -0
  301. package/plugin/channel/hub/local-roads.ts +89 -0
  302. package/plugin/channel/hub/remote-roads.ts +165 -0
  303. package/plugin/channel/hub/road-controller.ts +99 -0
  304. package/plugin/channel/hub-client.ts +149 -0
  305. package/plugin/channel/local-hub.js +5995 -0
  306. package/plugin/channel/local-hub.ts +349 -0
  307. package/plugin/channel/map-reporter.ts +48 -0
  308. package/plugin/channel/package-lock.json +1731 -0
  309. package/plugin/channel/package.json +19 -0
  310. package/plugin/channel/protocol.ts +98 -0
  311. package/plugin/channel/road-cli.ts +18 -0
  312. package/plugin/channel/run.sh +15 -0
  313. package/plugin/channel/runtime/claude.ts +458 -0
  314. package/plugin/channel/runtime/codex-config.ts +77 -0
  315. package/plugin/channel/runtime/codex.ts +669 -0
  316. package/plugin/channel/runtime/command.ts +62 -0
  317. package/plugin/channel/runtime/factory.ts +15 -0
  318. package/plugin/channel/runtime/json-rpc.ts +154 -0
  319. package/plugin/channel/runtime/kimi.ts +277 -0
  320. package/plugin/channel/runtime/opencode.ts +248 -0
  321. package/plugin/channel/runtime/process.ts +84 -0
  322. package/plugin/channel/runtime/types.ts +45 -0
  323. package/plugin/channel/runtime-files.ts +73 -0
  324. package/plugin/channel/runtime-gateway.js +6396 -0
  325. package/plugin/channel/runtime-gateway.ts +358 -0
  326. package/plugin/channel/runtime-metrics.ts +52 -0
  327. package/plugin/channel/runtime-subscription.ts +180 -0
  328. package/plugin/channel/terminal-delivery.ts +131 -0
  329. package/plugin/channel/untrusted.ts +60 -0
  330. package/plugin/commands/committee.md +31 -0
  331. package/plugin/commands/exit.md +32 -0
  332. package/plugin/commands/goals.md +20 -0
  333. package/plugin/commands/join.md +18 -0
  334. package/plugin/commands/notice.md +20 -0
  335. package/plugin/commands/propose.md +19 -0
  336. package/plugin/commands/round.md +17 -0
  337. package/plugin/commands/session.md +22 -0
  338. package/plugin/commands/settings.md +25 -0
  339. package/plugin/commands/setup.md +21 -0
  340. package/plugin/commands/team.md +23 -0
  341. package/plugin/domains/custom.md +27 -0
  342. package/plugin/domains/finance.md +31 -0
  343. package/plugin/domains/healthcare.md +34 -0
  344. package/plugin/domains/legal.md +31 -0
  345. package/plugin/domains/marketing.md +33 -0
  346. package/plugin/domains/operations.md +31 -0
  347. package/plugin/domains/research.md +32 -0
  348. package/plugin/domains/sales.md +30 -0
  349. package/plugin/domains/software.md +37 -0
  350. package/plugin/hooks/activity.sh +6 -0
  351. package/plugin/hooks/digging.sh +68 -0
  352. package/plugin/hooks/growth.sh +42 -0
  353. package/plugin/hooks/hooks.json +106 -0
  354. package/plugin/hooks/notice-on-pr.sh +35 -0
  355. package/plugin/hooks/notice-on-stop.sh +80 -0
  356. package/plugin/hooks/notice-pending.sh +58 -0
  357. package/plugin/hooks/solo-en-ciudad.sh +23 -0
  358. package/plugin/hooks/tokens.sh +47 -0
  359. package/plugin/roles/examples/account-executive.md +25 -0
  360. package/plugin/roles/examples/ai-manager.md +34 -0
  361. package/plugin/roles/examples/associate.md +24 -0
  362. package/plugin/roles/examples/brand-lead.md +20 -0
  363. package/plugin/roles/examples/cfo.md +20 -0
  364. package/plugin/roles/examples/city-lead.md +25 -0
  365. package/plugin/roles/examples/clinical-director.md +26 -0
  366. package/plugin/roles/examples/clinical-ops.md +25 -0
  367. package/plugin/roles/examples/clinician.md +25 -0
  368. package/plugin/roles/examples/compliance.md +27 -0
  369. package/plugin/roles/examples/content.md +24 -0
  370. package/plugin/roles/examples/controller.md +27 -0
  371. package/plugin/roles/examples/cpto.md +41 -0
  372. package/plugin/roles/examples/customer-success.md +25 -0
  373. package/plugin/roles/examples/data-engineer.md +37 -0
  374. package/plugin/roles/examples/data.md +60 -0
  375. package/plugin/roles/examples/dev.md +37 -0
  376. package/plugin/roles/examples/devops.md +39 -0
  377. package/plugin/roles/examples/enablement.md +24 -0
  378. package/plugin/roles/examples/ethics.md +24 -0
  379. package/plugin/roles/examples/fin-analytics.md +27 -0
  380. package/plugin/roles/examples/health-compliance.md +25 -0
  381. package/plugin/roles/examples/health-data.md +26 -0
  382. package/plugin/roles/examples/knowledge.md +25 -0
  383. package/plugin/roles/examples/lifecycle.md +26 -0
  384. package/plugin/roles/examples/llm-engineer.md +34 -0
  385. package/plugin/roles/examples/managing-partner.md +20 -0
  386. package/plugin/roles/examples/methods.md +23 -0
  387. package/plugin/roles/examples/operations-lead.md +23 -0
  388. package/plugin/roles/examples/ops.md +24 -0
  389. package/plugin/roles/examples/patient-safety.md +26 -0
  390. package/plugin/roles/examples/performance.md +27 -0
  391. package/plugin/roles/examples/po.md +36 -0
  392. package/plugin/roles/examples/process-owner.md +23 -0
  393. package/plugin/roles/examples/product-design.md +39 -0
  394. package/plugin/roles/examples/program-manager.md +24 -0
  395. package/plugin/roles/examples/quality.md +25 -0
  396. package/plugin/roles/examples/research-director.md +24 -0
  397. package/plugin/roles/examples/research-ops.md +25 -0
  398. package/plugin/roles/examples/researcher.md +25 -0
  399. package/plugin/roles/examples/revenue-lead.md +25 -0
  400. package/plugin/roles/examples/revops.md +26 -0
  401. package/plugin/roles/examples/seo.md +27 -0
  402. package/plugin/roles/examples/specialist.md +24 -0
  403. package/plugin/scripts/actualiza.py +198 -0
  404. package/plugin/scripts/admision.py +165 -0
  405. package/plugin/scripts/apaga.py +232 -0
  406. package/plugin/scripts/atajos.py +506 -0
  407. package/plugin/scripts/avatar.py +211 -0
  408. package/plugin/scripts/broker.py +531 -0
  409. package/plugin/scripts/cage.py +492 -0
  410. package/plugin/scripts/capabilities.py +210 -0
  411. package/plugin/scripts/card.py +322 -0
  412. package/plugin/scripts/cities.py +520 -0
  413. package/plugin/scripts/city-env.sh +90 -0
  414. package/plugin/scripts/city-runtime.sh +87 -0
  415. package/plugin/scripts/city-session.sh +552 -0
  416. package/plugin/scripts/city_env.py +67 -0
  417. package/plugin/scripts/crecimiento.py +140 -0
  418. package/plugin/scripts/deliberations.py +48 -0
  419. package/plugin/scripts/doctor.py +294 -0
  420. package/plugin/scripts/domains.py +201 -0
  421. package/plugin/scripts/evidencia.py +64 -0
  422. package/plugin/scripts/find-repos.sh +111 -0
  423. package/plugin/scripts/gh.py +159 -0
  424. package/plugin/scripts/hall_protocol.py +147 -0
  425. package/plugin/scripts/hook_activity.py +139 -0
  426. package/plugin/scripts/launch.py +94 -0
  427. package/plugin/scripts/logs.py +98 -0
  428. package/plugin/scripts/pairing.py +185 -0
  429. package/plugin/scripts/parcels.py +132 -0
  430. package/plugin/scripts/read-card.py +55 -0
  431. package/plugin/scripts/report.py +201 -0
  432. package/plugin/scripts/reset.py +180 -0
  433. package/plugin/scripts/roads.py +230 -0
  434. package/plugin/scripts/roles.py +274 -0
  435. package/plugin/scripts/runtime_log.py +69 -0
  436. package/plugin/scripts/runtime_processes.py +132 -0
  437. package/plugin/scripts/rutas.py +83 -0
  438. package/plugin/scripts/seat.py +1696 -0
  439. package/plugin/scripts/tokens.py +194 -0
  440. package/plugin/scripts/trust-repos.py +59 -0
  441. package/plugin/scripts/ui.py +280 -0
  442. package/plugin/scripts/units.py +80 -0
  443. package/plugin/scripts/workspace.py +561 -0
  444. package/plugin/skills/city/SKILL.md +191 -0
  445. package/templates/blank.md +39 -0
  446. package/templates/finance.md +44 -0
  447. package/templates/legal.md +45 -0
  448. package/templates/marketing.md +48 -0
  449. package/templates/product.md +45 -0
@@ -0,0 +1,506 @@
1
+ #!/usr/bin/env python3
2
+ """A city on your desktop: one icon, its name under it, double-click to enter.
3
+
4
+ A city already has an identity, a name and a colour. What it did not have was a
5
+ door somebody can find without remembering a command — and a tool you have to
6
+ remember how to start is a tool you stop starting. So each city can put a real
7
+ desktop shortcut next to the others: a macOS `.app` bundle, or a Linux
8
+ `.desktop` entry, carrying an icon drawn from the city's own identity.
9
+
10
+ Everything here is stdlib. The icon is a PNG written by hand (zlib plus four
11
+ chunks) so no image library is required, and on macOS `iconutil` — which ships
12
+ with the system — turns it into the `.icns` a bundle wants. If any of that is
13
+ missing the shortcut is still written with whatever icon the desktop gives it,
14
+ because a door with a plain icon beats no door.
15
+
16
+ The shortcut runs the same front door you would type: `agents-city seat --city
17
+ <name>`, or the hall with `--hall`. It is not a second implementation of
18
+ anything; it is a labelled button on the one that exists.
19
+ """
20
+
21
+ import binascii
22
+ import hashlib
23
+ import os
24
+ import plistlib
25
+ import shutil
26
+ import struct
27
+ import subprocess
28
+ import sys
29
+ import zlib
30
+
31
+ GUIONES = os.path.dirname(os.path.abspath(__file__))
32
+ sys.path.insert(0, GUIONES)
33
+ import cities # noqa: E402
34
+
35
+ #: The palette a city's icon is tinted from — the map's own house colours, so a
36
+ #: city looks on the desktop like it looks on its map.
37
+ PALETA = (
38
+ (0x3B, 0x82, 0xF6),
39
+ (0x8B, 0x5C, 0xF6),
40
+ (0xF5, 0x9E, 0x0B),
41
+ (0x3F, 0xB8, 0xA0),
42
+ (0xEF, 0x44, 0x44),
43
+ (0x10, 0xB9, 0x81),
44
+ )
45
+
46
+ LADO = 512
47
+
48
+
49
+ def color_de(identidad):
50
+ """One city, one colour, forever — derived, never stored, never random."""
51
+ marca = hashlib.sha256(str(identidad).encode()).digest()
52
+ return PALETA[marca[0] % len(PALETA)]
53
+
54
+
55
+ def _trozo(etiqueta, datos):
56
+ return (
57
+ struct.pack(">I", len(datos))
58
+ + etiqueta
59
+ + datos
60
+ + struct.pack(">I", binascii.crc32(etiqueta + datos) & 0xFFFFFFFF)
61
+ )
62
+
63
+
64
+ def png(pixeles, lado):
65
+ """A PNG from RGBA rows, by hand: zlib and four chunks, no dependencies."""
66
+ crudo = b"".join(b"\x00" + bytes(fila) for fila in pixeles)
67
+ return b"".join(
68
+ [
69
+ b"\x89PNG\r\n\x1a\n",
70
+ _trozo(b"IHDR", struct.pack(">IIBBBBB", lado, lado, 8, 6, 0, 0, 0)),
71
+ _trozo(b"IDAT", zlib.compress(crudo, 9)),
72
+ _trozo(b"IEND", b""),
73
+ ]
74
+ )
75
+
76
+
77
+ def _casa(x, y, lado, color):
78
+ """One isometric house on a rounded tile — the map's own shape, in small.
79
+
80
+ Drawn arithmetically rather than from an asset: the icon must exist for a
81
+ city created on a machine that never cloned this repo.
82
+ """
83
+ r, g, b = color
84
+ cx, cy = lado / 2, lado / 2
85
+ borde = lado * 0.08
86
+ # The rounded square the whole icon sits on, as macOS and GNOME both expect.
87
+ dx = max(abs(x - cx) - (lado / 2 - borde - lado * 0.06), 0)
88
+ dy = max(abs(y - cy) - (lado / 2 - borde - lado * 0.06), 0)
89
+ if (dx * dx + dy * dy) ** 0.5 > lado * 0.06:
90
+ return (0, 0, 0, 0)
91
+ # An isometric box: the roof rhombus, and the same rhombus swept downwards
92
+ # to make the two walls. Three tints of one colour, because a silhouette
93
+ # with no shading is a blob at 32 px, which is the size that matters.
94
+ fondo = (int(r * 0.18), int(g * 0.18), int(b * 0.22), 255)
95
+ u = (x - cx) / (lado * 0.34)
96
+ v = (y - cy + lado * 0.06) / (lado * 0.34)
97
+ alto = 0.62 # how tall the walls stand under the roof
98
+ dentro_v = min(max(v, 0.0), alto) # clamp: v above the roof, below the base
99
+ if abs(u) + 2 * abs(v - dentro_v) > 1:
100
+ return fondo
101
+ if v <= 0: # the roof, catching the light
102
+ return (min(255, r + 45), min(255, g + 45), min(255, b + 45), 255)
103
+ f = 0.78 if u < 0 else 0.52 # left wall lit, right wall in shade
104
+ return (int(r * f), int(g * f), int(b * f), 255)
105
+
106
+
107
+ def icono_png(identidad, lado=LADO):
108
+ """The city's icon as PNG bytes: a house in the city's own colour."""
109
+ color = color_de(identidad)
110
+ filas = []
111
+ for y in range(lado):
112
+ fila = bytearray()
113
+ for x in range(lado):
114
+ fila += bytes(_casa(x + 0.5, y + 0.5, lado, color))
115
+ filas.append(fila)
116
+ return png(filas, lado)
117
+
118
+
119
+ def ico(identidad, lado=256):
120
+ """The city's icon as Windows `.ico` bytes.
121
+
122
+ An ICO is a tiny header around image data, and since Vista that data may be
123
+ a PNG as-is — so the same hand-written PNG serves Windows with six bytes of
124
+ ceremony, no converter and no dependency.
125
+ """
126
+ imagen = icono_png(identidad, lado)
127
+ ancho = 0 if lado >= 256 else lado # 0 means 256 in an ICO directory
128
+ cabecera = struct.pack("<HHH", 0, 1, 1)
129
+ entrada = struct.pack(
130
+ "<BBBBHHII", ancho, ancho, 0, 0, 1, 32, len(imagen), len(cabecera) + 16
131
+ )
132
+ return cabecera + entrada + imagen
133
+
134
+
135
+ #: The sizes an .icns carries, as (pixels, filename). Each one is DRAWN at its
136
+ #: own size rather than resampled from a big one: the shape is arithmetic, so a
137
+ #: 32px icon drawn as 32px has crisper edges than a downscaled 512px one — and
138
+ #: it costs no resampler, which is why nothing here shells out to `sips`.
139
+ _TAMANOS_ICNS = (
140
+ (16, "icon_16x16.png"),
141
+ (32, "icon_16x16@2x.png"),
142
+ (32, "icon_32x32.png"),
143
+ (64, "icon_32x32@2x.png"),
144
+ (128, "icon_128x128.png"),
145
+ (256, "icon_128x128@2x.png"),
146
+ (256, "icon_256x256.png"),
147
+ (512, "icon_256x256@2x.png"),
148
+ (512, "icon_512x512.png"),
149
+ )
150
+
151
+
152
+ def _icns(identidad, destino_icns):
153
+ """Build a macOS .icns for this city, or False when the system cannot.
154
+
155
+ `iconutil` ships with macOS and is the only outside program involved: the
156
+ images themselves are drawn here, at each size the bundle wants.
157
+ """
158
+ if not shutil.which("iconutil"):
159
+ return False
160
+ juego = destino_icns + ".iconset"
161
+ os.makedirs(juego, exist_ok=True)
162
+ try:
163
+ dibujados = {}
164
+ for lado, nombre in _TAMANOS_ICNS:
165
+ if lado not in dibujados:
166
+ dibujados[lado] = icono_png(identidad, lado)
167
+ with open(os.path.join(juego, nombre), "wb") as f:
168
+ f.write(dibujados[lado])
169
+ hecho = subprocess.run(
170
+ ["iconutil", "-c", "icns", juego, "-o", destino_icns],
171
+ capture_output=True,
172
+ timeout=60,
173
+ )
174
+ return hecho.returncode == 0 and os.path.isfile(destino_icns)
175
+ except (OSError, subprocess.SubprocessError):
176
+ return False
177
+ finally:
178
+ shutil.rmtree(juego, ignore_errors=True)
179
+
180
+
181
+ def en_wsl():
182
+ """Whether this Linux is really Windows wearing a Linux coat.
183
+
184
+ It matters here more than anywhere else in the product: under WSL a
185
+ `~/Desktop` is the Linux home's desktop, which nobody ever looks at. The
186
+ desktop a person actually sees belongs to Windows.
187
+ """
188
+ if os.environ.get("WSL_DISTRO_NAME") or os.environ.get("WSL_INTEROP"):
189
+ return True
190
+ try:
191
+ with open("/proc/version", encoding="utf-8", errors="replace") as f:
192
+ return "microsoft" in f.read().lower()
193
+ except OSError:
194
+ return False
195
+
196
+
197
+ def _powershell():
198
+ return shutil.which("powershell.exe") or shutil.which("pwsh.exe")
199
+
200
+
201
+ def escritorio_windows():
202
+ """The Windows desktop, as a path this side of WSL can write, or ''.
203
+
204
+ Asked of Windows itself rather than assembled from a username guess:
205
+ a redirected desktop (OneDrive, a domain profile) lives nowhere near
206
+ C:\\Users\\<name>\\Desktop, and guessing would write into a folder the
207
+ person never sees.
208
+ """
209
+ consola = _powershell()
210
+ if not consola or not shutil.which("wslpath"):
211
+ return ""
212
+ try:
213
+ salida = subprocess.run(
214
+ [consola, "-NoProfile", "-Command", "[Environment]::GetFolderPath('Desktop')"],
215
+ capture_output=True,
216
+ text=True,
217
+ timeout=30,
218
+ )
219
+ ventana = salida.stdout.strip()
220
+ if salida.returncode != 0 or not ventana:
221
+ return ""
222
+ traducida = subprocess.run(
223
+ ["wslpath", "-u", ventana], capture_output=True, text=True, timeout=15
224
+ )
225
+ ruta = traducida.stdout.strip()
226
+ return ruta if traducida.returncode == 0 and os.path.isdir(ruta) else ""
227
+ except (OSError, subprocess.SubprocessError):
228
+ return ""
229
+
230
+
231
+ def escritorio():
232
+ """Where the desktop is, or '' when this machine has none."""
233
+ forzado = os.environ.get("CITY_DESKTOP")
234
+ if forzado:
235
+ ruta = os.path.expanduser(forzado)
236
+ return ruta if os.path.isdir(ruta) else ""
237
+ if en_wsl():
238
+ # The Windows desktop first: it is the one with icons on it. A WSL
239
+ # install with no interop still gets its Linux desktop, which at least
240
+ # a Linux desktop environment inside WSLg can show.
241
+ ventana = escritorio_windows()
242
+ if ventana:
243
+ return ventana
244
+ casa = os.path.expanduser("~/Desktop")
245
+ return casa if os.path.isdir(casa) else ""
246
+
247
+
248
+ def orden_de_ciudad(datos, hall=False):
249
+ """The command the shortcut runs — the same one a person would type."""
250
+ nombre = cities.nombre(datos)
251
+ programa = "agents-city"
252
+ return f"{programa} {'hall' if hall else 'seat'} --city {_entrecomilla(nombre)}"
253
+
254
+
255
+ def _entrecomilla(valor):
256
+ return "'" + str(valor).replace("'", "'\\''") + "'"
257
+
258
+
259
+ def _guion_mac(orden):
260
+ """Open Terminal and run the city there — a city is a tmux session, and a
261
+ session with nowhere to draw itself is not an open city."""
262
+ escapada = orden.replace("\\", "\\\\").replace('"', '\\"')
263
+ return (
264
+ "#!/bin/sh\n"
265
+ "# Written by agents-city. Delete this bundle to remove the shortcut.\n"
266
+ "exec /usr/bin/osascript "
267
+ f'-e "tell application \\"Terminal\\" to do script \\"{escapada}\\"" '
268
+ '-e "tell application \\"Terminal\\" to activate"\n'
269
+ )
270
+
271
+
272
+ def _crea_mac(datos, carpeta, nombre, orden, identidad):
273
+ paquete = os.path.join(carpeta, f"{nombre}.app")
274
+ if os.path.exists(paquete):
275
+ shutil.rmtree(paquete, ignore_errors=True)
276
+ macos = os.path.join(paquete, "Contents", "MacOS")
277
+ recursos = os.path.join(paquete, "Contents", "Resources")
278
+ os.makedirs(macos)
279
+ os.makedirs(recursos)
280
+ ejecutable = os.path.join(macos, "abrir-ciudad")
281
+ with open(ejecutable, "w", encoding="utf-8") as f:
282
+ f.write(_guion_mac(orden))
283
+ os.chmod(ejecutable, 0o755)
284
+ info = {
285
+ "CFBundleName": nombre,
286
+ "CFBundleDisplayName": nombre,
287
+ "CFBundleExecutable": "abrir-ciudad",
288
+ "CFBundleIdentifier": f"com.arkatai.agents-city.{cities.slug(datos) or 'city'}",
289
+ "CFBundlePackageType": "APPL",
290
+ "CFBundleInfoDictionaryVersion": "6.0",
291
+ "CFBundleShortVersionString": "1.0",
292
+ "LSApplicationCategoryType": "public.app-category.developer-tools",
293
+ }
294
+ if _icns(identidad, os.path.join(recursos, "city.icns")):
295
+ info["CFBundleIconFile"] = "city"
296
+ else:
297
+ # No iconutil: keep the plain PNG in the bundle so the icon still
298
+ # exists on disk for anybody who wants to set it by hand.
299
+ with open(os.path.join(recursos, "city.png"), "wb") as f:
300
+ f.write(icono_png(identidad))
301
+ with open(os.path.join(paquete, "Contents", "Info.plist"), "wb") as f:
302
+ plistlib.dump(info, f)
303
+ return paquete
304
+
305
+
306
+ def _crea_linux(datos, carpeta, nombre, orden, identidad):
307
+ iconos = os.path.join(
308
+ os.path.expanduser(os.environ.get("XDG_DATA_HOME") or "~/.local/share"),
309
+ "agents-city",
310
+ "icons",
311
+ )
312
+ os.makedirs(iconos, exist_ok=True)
313
+ icono = os.path.join(iconos, f"{cities.slug(datos) or 'city'}.png")
314
+ with open(icono, "wb") as f:
315
+ f.write(icono_png(identidad, 256))
316
+ atajo = os.path.join(carpeta, f"{cities.slug(datos) or 'city'}.desktop")
317
+ with open(atajo, "w", encoding="utf-8") as f:
318
+ f.write(
319
+ "[Desktop Entry]\n"
320
+ "Type=Application\n"
321
+ "Version=1.0\n"
322
+ f"Name={nombre}\n"
323
+ "Comment=Open this Agents City city\n"
324
+ f"Exec={orden}\n"
325
+ f"Icon={icono}\n"
326
+ "Terminal=true\n"
327
+ "Categories=Development;\n"
328
+ )
329
+ os.chmod(atajo, 0o755)
330
+ # GNOME refuses to launch a .desktop it does not trust; best effort, and a
331
+ # failure here still leaves a file the person can mark themselves.
332
+ if shutil.which("gio"):
333
+ try:
334
+ subprocess.run(
335
+ ["gio", "set", atajo, "metadata::trusted", "true"],
336
+ capture_output=True,
337
+ timeout=10,
338
+ )
339
+ except (OSError, subprocess.SubprocessError):
340
+ pass
341
+ return atajo
342
+
343
+
344
+ def _crea_wsl(datos, carpeta, nombre, orden, identidad):
345
+ """A real Windows shortcut for a city that lives inside WSL.
346
+
347
+ The city is a tmux session in Linux, so the shortcut has to cross back: it
348
+ launches `wsl.exe -d <distro>` running the same front-door command in a
349
+ login shell. A `.lnk` is a COM object, so Windows builds it — through its
350
+ own PowerShell — and that is also what lets it carry an icon. Without
351
+ interop we still write a `.cmd`, which double-clicks fine and only lacks
352
+ the icon; a door with a plain icon beats no door.
353
+ """
354
+ distro = os.environ.get("WSL_DISTRO_NAME", "")
355
+ dentro = f"cd ~ && {orden}".replace('"', '\\"')
356
+ eleccion = f"-d {distro} " if distro else ""
357
+ argumentos = f'{eleccion}-- bash -lic "{dentro}"'
358
+ icono_win = ""
359
+ consola = _powershell()
360
+ perfil = os.path.join(carpeta, "..")
361
+ iconos = os.path.join(perfil, ".agents-city-icons")
362
+ try:
363
+ os.makedirs(iconos, exist_ok=True)
364
+ icono_win = os.path.join(iconos, f"{cities.slug(datos) or 'city'}.ico")
365
+ with open(icono_win, "wb") as f:
366
+ f.write(ico(identidad))
367
+ except OSError:
368
+ icono_win = ""
369
+
370
+ if consola and shutil.which("wslpath"):
371
+ atajo = os.path.join(carpeta, f"{nombre}.lnk")
372
+ try:
373
+ destino_win = subprocess.run(
374
+ ["wslpath", "-w", atajo], capture_output=True, text=True, timeout=15
375
+ ).stdout.strip()
376
+ icono_ruta = (
377
+ subprocess.run(
378
+ ["wslpath", "-w", icono_win], capture_output=True, text=True, timeout=15
379
+ ).stdout.strip()
380
+ if icono_win
381
+ else ""
382
+ )
383
+ guion = (
384
+ "$s = (New-Object -ComObject WScript.Shell).CreateShortcut('"
385
+ + destino_win.replace("'", "''")
386
+ + "'); $s.TargetPath = 'wsl.exe'; $s.Arguments = '"
387
+ + argumentos.replace("'", "''")
388
+ + "'; $s.Description = 'Open the "
389
+ + nombre.replace("'", "''")
390
+ + " city'; "
391
+ + (f"$s.IconLocation = '{icono_ruta}';" if icono_ruta else "")
392
+ + " $s.Save()"
393
+ )
394
+ hecho = subprocess.run(
395
+ [consola, "-NoProfile", "-Command", guion],
396
+ capture_output=True,
397
+ text=True,
398
+ timeout=60,
399
+ )
400
+ if hecho.returncode == 0 and os.path.exists(atajo):
401
+ return atajo
402
+ except (OSError, subprocess.SubprocessError):
403
+ pass
404
+
405
+ atajo = os.path.join(carpeta, f"{nombre}.cmd")
406
+ with open(atajo, "w", encoding="utf-8", newline="\r\n") as f:
407
+ f.write(
408
+ "@echo off\r\n"
409
+ ":: Written by agents-city. Delete this file to remove the shortcut.\r\n"
410
+ f"wsl.exe {argumentos}\r\n"
411
+ )
412
+ os.chmod(atajo, 0o755)
413
+ return atajo
414
+
415
+
416
+ def crea(datos, hall=False, carpeta=""):
417
+ """Put this city on the desktop. Returns (path, '') or ('', why)."""
418
+ carpeta = carpeta or escritorio()
419
+ if not carpeta:
420
+ return "", "this machine has no Desktop folder; pass --to <folder>"
421
+ if not os.path.isdir(datos):
422
+ return "", f"there is no city at {datos}"
423
+ nombre = cities.nombre(datos) or "City"
424
+ orden = orden_de_ciudad(datos, hall)
425
+ identidad = cities.identidad(datos) or nombre
426
+ try:
427
+ if sys.platform == "darwin":
428
+ return _crea_mac(datos, carpeta, nombre, orden, identidad), ""
429
+ if en_wsl():
430
+ return _crea_wsl(datos, carpeta, nombre, orden, identidad), ""
431
+ return _crea_linux(datos, carpeta, nombre, orden, identidad), ""
432
+ except OSError as e:
433
+ return "", f"could not write the shortcut: {e}"
434
+
435
+
436
+ def quita(datos, carpeta=""):
437
+ """Remove this city's shortcut. Returns True when one was there."""
438
+ carpeta = carpeta or escritorio()
439
+ if not carpeta:
440
+ return False
441
+ candidatos = [
442
+ os.path.join(carpeta, f"{cities.nombre(datos)}.app"),
443
+ os.path.join(carpeta, f"{cities.slug(datos) or 'city'}.desktop"),
444
+ os.path.join(carpeta, f"{cities.nombre(datos)}.lnk"),
445
+ os.path.join(carpeta, f"{cities.nombre(datos)}.cmd"),
446
+ ]
447
+ quitados = False
448
+ for ruta in candidatos:
449
+ if os.path.isdir(ruta):
450
+ shutil.rmtree(ruta, ignore_errors=True)
451
+ quitados = True
452
+ elif os.path.isfile(ruta):
453
+ os.unlink(ruta)
454
+ quitados = True
455
+ return quitados
456
+
457
+
458
+ def _uso():
459
+ print(
460
+ " usage: agents-city shortcut [city] [--hall] [--remove] [--to <folder>]\n\n"
461
+ " Put a city on your desktop: its icon, its name, double-click to enter.\n"
462
+ " Without a city name it uses the one you are in.\n\n"
463
+ " --hall open the town hall in the browser instead of the seat\n"
464
+ " --remove take the shortcut off the desktop\n"
465
+ " --to DIR write it somewhere else than ~/Desktop\n"
466
+ )
467
+
468
+
469
+ def main():
470
+ args = sys.argv[1:]
471
+ if any(a in ("-h", "--help", "help") for a in args):
472
+ _uso()
473
+ return 0
474
+ hall = "--hall" in args
475
+ quitar = "--remove" in args
476
+ carpeta = ""
477
+ if "--to" in args:
478
+ i = args.index("--to")
479
+ carpeta = os.path.expanduser(args[i + 1]) if len(args) > i + 1 else ""
480
+ del args[i:i + 2]
481
+ pedida = next((a for a in args if not a.startswith("-")), "")
482
+ usuario = cities.usuario_actual()
483
+ datos = cities.resuelve(pedida, usuario) if pedida else cities.actual(usuario)
484
+ if not datos:
485
+ print(f" No city called {pedida!r}.", file=sys.stderr)
486
+ return 1
487
+ if quitar:
488
+ print(
489
+ f" Shortcut removed for {cities.nombre(datos)}."
490
+ if quita(datos, carpeta)
491
+ else f" {cities.nombre(datos)} had no shortcut on the desktop."
492
+ )
493
+ return 0
494
+ ruta, mal = crea(datos, hall, carpeta)
495
+ if mal:
496
+ print(f" {mal}", file=sys.stderr)
497
+ return 1
498
+ print(
499
+ f" {cities.nombre(datos)} is on your desktop: {ruta.replace(os.path.expanduser('~'), '~')}"
500
+ f"\n Double-click it to open {'the hall' if hall else 'the city'}."
501
+ )
502
+ return 0
503
+
504
+
505
+ if __name__ == "__main__":
506
+ sys.exit(main())