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.
- package/.claude-plugin/marketplace.json +16 -0
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.es.md +2226 -0
- package/README.md +2196 -0
- package/benchmarks/committee/README.md +39 -0
- package/benchmarks/committee/metrics.py +51 -0
- package/benchmarks/committee/run.py +64 -0
- package/benchmarks/committee/traces/01-single.json +8 -0
- package/benchmarks/committee/traces/02-mesh.json +16 -0
- package/benchmarks/committee/traces/03-chair.json +15 -0
- package/benchmarks/latency/README.md +35 -0
- package/benchmarks/latency/fake-claude-cli.mjs +119 -0
- package/benchmarks/latency/fake-native-server.mjs +340 -0
- package/benchmarks/latency/fake-tui.py +91 -0
- package/benchmarks/latency/live.py +380 -0
- package/benchmarks/stress/README.md +20 -0
- package/benchmarks/stress/run.py +487 -0
- package/bin/agents +4 -0
- package/bin/agents-city.js +119 -0
- package/bin/benchmark +17 -0
- package/bin/bus +6 -0
- package/bin/cities +3 -0
- package/bin/city +86 -0
- package/bin/committee +6 -0
- package/bin/demo +200 -0
- package/bin/doctor +3 -0
- package/bin/exit +5 -0
- package/bin/hall +73 -0
- package/bin/hall.html +498 -0
- package/bin/logs +3 -0
- package/bin/navegador.mjs +243 -0
- package/bin/report.py +14 -0
- package/bin/reset +3 -0
- package/bin/road +3 -0
- package/bin/seat +10 -0
- package/bin/serve.py +1579 -0
- package/bin/setup.py +753 -0
- package/bin/shortcut +3 -0
- package/bin/skills +3 -0
- package/bin/test +133 -0
- package/bin/test-actualiza.py +130 -0
- package/bin/test-adapter.py +183 -0
- package/bin/test-admision.py +99 -0
- package/bin/test-atajos.py +301 -0
- package/bin/test-avatar.py +58 -0
- package/bin/test-benchmark.py +40 -0
- package/bin/test-broker.py +334 -0
- package/bin/test-cage.py +374 -0
- package/bin/test-card.py +402 -0
- package/bin/test-channel.py +379 -0
- package/bin/test-cities.py +453 -0
- package/bin/test-claude-runtime.py +252 -0
- package/bin/test-committee.py +340 -0
- package/bin/test-contracts.py +551 -0
- package/bin/test-crecimiento.py +164 -0
- package/bin/test-demo.py +308 -0
- package/bin/test-doctor.py +139 -0
- package/bin/test-domains.py +151 -0
- package/bin/test-evidencia.py +50 -0
- package/bin/test-exit.py +128 -0
- package/bin/test-hall-protocol.py +93 -0
- package/bin/test-launch.py +169 -0
- package/bin/test-live-feed.py +508 -0
- package/bin/test-navegador.py +140 -0
- package/bin/test-pairing.py +99 -0
- package/bin/test-parcels.py +283 -0
- package/bin/test-runtime-failures.py +252 -0
- package/bin/test-runtime-ui.py +488 -0
- package/bin/test-runtime.py +268 -0
- package/bin/test-rutas.py +80 -0
- package/bin/test-seat.py +1598 -0
- package/bin/test-security.py +150 -0
- package/bin/test-serve.py +1251 -0
- package/bin/test-stress.py +64 -0
- package/bin/test-widgets.py +143 -0
- package/bin/test-workspace.py +251 -0
- package/bin/testlib.py +118 -0
- package/bin/tokens.py +14 -0
- package/bin/update +3 -0
- package/bus/scripts/deploy.sh +60 -0
- package/bus/scripts/mint-token.sh +68 -0
- package/bus/scripts/setup-dev.sh +79 -0
- package/bus/scripts/test-channel.ts +8 -0
- package/bus/scripts/test-hub.ts +266 -0
- package/bus/scripts/test-local.sh +40 -0
- package/bus/scripts/test-queue.ts +132 -0
- package/bus/worker/package-lock.json +1971 -0
- package/bus/worker/package.json +17 -0
- package/bus/worker/src/index.ts +485 -0
- package/bus/worker/tsconfig.json +13 -0
- package/bus/worker/wrangler.toml +32 -0
- package/city/oven/README.md +59 -0
- package/city/oven/collect.py +43 -0
- package/city/oven/oven.html +4 -0
- package/city/scripts/history.py +120 -0
- package/city/scripts/seed.py +442 -0
- package/city/web/assets/modelos/License.txt +28 -0
- package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/assets/modelos/building-a.glb +0 -0
- package/city/web/assets/modelos/building-b.glb +0 -0
- package/city/web/assets/modelos/building-c.glb +0 -0
- package/city/web/assets/modelos/building-d.glb +0 -0
- package/city/web/assets/modelos/building-e.glb +0 -0
- package/city/web/assets/modelos/building-f.glb +0 -0
- package/city/web/assets/modelos/building-g.glb +0 -0
- package/city/web/assets/modelos/building-h.glb +0 -0
- package/city/web/assets/sprites/building-a.png +0 -0
- package/city/web/assets/sprites/building-b.png +0 -0
- package/city/web/assets/sprites/building-c.png +0 -0
- package/city/web/assets/sprites/building-d.png +0 -0
- package/city/web/assets/sprites/building-e.png +0 -0
- package/city/web/assets/sprites/building-f.png +0 -0
- package/city/web/assets/sprites/building-g.png +0 -0
- package/city/web/assets/sprites/building-h.png +0 -0
- package/city/web/assets/sprites/building-i.png +0 -0
- package/city/web/assets/sprites/building-j.png +0 -0
- package/city/web/assets/sprites/building-k.png +0 -0
- package/city/web/assets/sprites/building-l.png +0 -0
- package/city/web/assets/sprites/building-m.png +0 -0
- package/city/web/assets/sprites/building-n.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
- package/city/web/assets/sprites/catalogo.json +47 -0
- package/city/web/assets/sprites/detail-awning-wide.png +0 -0
- package/city/web/assets/sprites/detail-awning.png +0 -0
- package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
- package/city/web/assets/sprites/detail-overhang.png +0 -0
- package/city/web/assets/sprites/detail-parasol-a.png +0 -0
- package/city/web/assets/sprites/detail-parasol-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-c.png +0 -0
- package/city/web/assets/sprites/low-detail-building-d.png +0 -0
- package/city/web/assets/sprites/low-detail-building-e.png +0 -0
- package/city/web/assets/sprites/low-detail-building-f.png +0 -0
- package/city/web/assets/sprites/low-detail-building-g.png +0 -0
- package/city/web/assets/sprites/low-detail-building-h.png +0 -0
- package/city/web/assets/sprites/low-detail-building-i.png +0 -0
- package/city/web/assets/sprites/low-detail-building-j.png +0 -0
- package/city/web/assets/sprites/low-detail-building-k.png +0 -0
- package/city/web/assets/sprites/low-detail-building-l.png +0 -0
- package/city/web/assets/sprites/low-detail-building-m.png +0 -0
- package/city/web/assets/sprites/low-detail-building-n.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/assets/sprites/medidas.json +248 -0
- package/city/web/dist/city.js +939 -0
- package/city/web/dist/index.html +294 -0
- package/city/web/dist/modelos/License.txt +28 -0
- package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/dist/modelos/building-a.glb +0 -0
- package/city/web/dist/modelos/building-b.glb +0 -0
- package/city/web/dist/modelos/building-c.glb +0 -0
- package/city/web/dist/modelos/building-d.glb +0 -0
- package/city/web/dist/modelos/building-e.glb +0 -0
- package/city/web/dist/modelos/building-f.glb +0 -0
- package/city/web/dist/modelos/building-g.glb +0 -0
- package/city/web/dist/modelos/building-h.glb +0 -0
- package/city/web/dist/sprites/building-a.png +0 -0
- package/city/web/dist/sprites/building-b.png +0 -0
- package/city/web/dist/sprites/building-c.png +0 -0
- package/city/web/dist/sprites/building-d.png +0 -0
- package/city/web/dist/sprites/building-e.png +0 -0
- package/city/web/dist/sprites/building-f.png +0 -0
- package/city/web/dist/sprites/building-g.png +0 -0
- package/city/web/dist/sprites/building-h.png +0 -0
- package/city/web/dist/sprites/building-i.png +0 -0
- package/city/web/dist/sprites/building-j.png +0 -0
- package/city/web/dist/sprites/building-k.png +0 -0
- package/city/web/dist/sprites/building-l.png +0 -0
- package/city/web/dist/sprites/building-m.png +0 -0
- package/city/web/dist/sprites/building-n.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
- package/city/web/dist/sprites/catalogo.json +47 -0
- package/city/web/dist/sprites/detail-awning-wide.png +0 -0
- package/city/web/dist/sprites/detail-awning.png +0 -0
- package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
- package/city/web/dist/sprites/detail-overhang.png +0 -0
- package/city/web/dist/sprites/detail-parasol-a.png +0 -0
- package/city/web/dist/sprites/detail-parasol-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-c.png +0 -0
- package/city/web/dist/sprites/low-detail-building-d.png +0 -0
- package/city/web/dist/sprites/low-detail-building-e.png +0 -0
- package/city/web/dist/sprites/low-detail-building-f.png +0 -0
- package/city/web/dist/sprites/low-detail-building-g.png +0 -0
- package/city/web/dist/sprites/low-detail-building-h.png +0 -0
- package/city/web/dist/sprites/low-detail-building-i.png +0 -0
- package/city/web/dist/sprites/low-detail-building-j.png +0 -0
- package/city/web/dist/sprites/low-detail-building-k.png +0 -0
- package/city/web/dist/sprites/low-detail-building-l.png +0 -0
- package/city/web/dist/sprites/low-detail-building-m.png +0 -0
- package/city/web/dist/sprites/low-detail-building-n.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/dist/sprites/medidas.json +248 -0
- package/city/web/dist-hall/hall.js +1363 -0
- package/city/web/index.html +294 -0
- package/city/web/package.json +18 -0
- package/city/web/sella.py +21 -0
- package/city/web/src/activity-actors.ts +47 -0
- package/city/web/src/activity.ts +172 -0
- package/city/web/src/ayuntamiento.ts +580 -0
- package/city/web/src/draw.ts +640 -0
- package/city/web/src/game-speech.ts +79 -0
- package/city/web/src/hall.ts +1959 -0
- package/city/web/src/main.ts +2252 -0
- package/city/web/src/oven.ts +124 -0
- package/city/web/src/people.ts +244 -0
- package/city/web/src/presencia.ts +193 -0
- package/city/web/src/puertas.ts +180 -0
- package/city/web/src/repo-roles.ts +59 -0
- package/city/worker/package.json +16 -0
- package/city/worker/schema.sql +117 -0
- package/city/worker/src/index.ts +702 -0
- package/city/worker/src/square.ts +218 -0
- package/city/worker/wrangler.toml +58 -0
- package/demo/ada.md +50 -0
- package/demo/bruno.md +62 -0
- package/demo/camila.md +56 -0
- package/demo/city/ada.md +40 -0
- package/demo/city/city.yml +8 -0
- package/demo/city/parcels.yml +15 -0
- package/demo/city/roads.json +4 -0
- package/demo/city/units.yml +10 -0
- package/demo/clinica/city.yml +8 -0
- package/demo/clinica/parcels.yml +15 -0
- package/demo/clinica/roads.json +4 -0
- package/demo/clinica/units.yml +9 -0
- package/demo/clinica/vera.md +46 -0
- package/demo/dante.md +54 -0
- package/demo/despacho/city.yml +8 -0
- package/demo/despacho/marta.md +46 -0
- package/demo/despacho/parcels.yml +15 -0
- package/demo/despacho/roads.json +4 -0
- package/demo/despacho/units.yml +9 -0
- package/demo/elsa.md +50 -0
- package/demo/farid.md +52 -0
- package/demo/greta.md +50 -0
- package/demo/hugo.md +49 -0
- package/demo/iris.md +49 -0
- package/demo/jonas.md +51 -0
- package/demo/kira.md +51 -0
- package/demo/luca.md +49 -0
- package/demo/parcels.yml +92 -0
- package/demo/seed.py +20 -0
- package/demo/show.py +161 -0
- package/demo/stories.py +824 -0
- package/demo/units.yml +33 -0
- package/docs/agents-first.md +106 -0
- package/docs/glossary.md +55 -0
- package/docs/map-live-layers.md +73 -0
- package/docs/security.md +265 -0
- package/docs/self-host.md +151 -0
- package/docs/testing.md +205 -0
- package/package.json +73 -0
- package/plugin/.claude-plugin/plugin.json +62 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/channel/activity-cli.ts +16 -0
- package/plugin/channel/adapter-prompts.ts +113 -0
- package/plugin/channel/adapter.js +4292 -0
- package/plugin/channel/adapter.ts +133 -0
- package/plugin/channel/bus.js +20188 -0
- package/plugin/channel/bus.ts +131 -0
- package/plugin/channel/city-config.ts +125 -0
- package/plugin/channel/claude-channel.ts +58 -0
- package/plugin/channel/cli-args.ts +75 -0
- package/plugin/channel/client.js +4200 -0
- package/plugin/channel/client.ts +38 -0
- package/plugin/channel/committee/activity.ts +256 -0
- package/plugin/channel/committee/collection.ts +154 -0
- package/plugin/channel/committee/decision.ts +175 -0
- package/plugin/channel/committee/floor.ts +135 -0
- package/plugin/channel/committee/guards.ts +30 -0
- package/plugin/channel/committee/history.ts +38 -0
- package/plugin/channel/committee/render.ts +102 -0
- package/plugin/channel/committee/service.ts +72 -0
- package/plugin/channel/committee/storage.ts +92 -0
- package/plugin/channel/committee/types.ts +163 -0
- package/plugin/channel/committee/view.ts +63 -0
- package/plugin/channel/committee-cli.ts +109 -0
- package/plugin/channel/delivery-metrics.ts +61 -0
- package/plugin/channel/delivery-queue.ts +118 -0
- package/plugin/channel/hub/activity-controller.ts +72 -0
- package/plugin/channel/hub/activity-feed.ts +169 -0
- package/plugin/channel/hub/committee-controller.ts +60 -0
- package/plugin/channel/hub/connections.ts +48 -0
- package/plugin/channel/hub/diagnostics.ts +67 -0
- package/plugin/channel/hub/envelope-validity.ts +109 -0
- package/plugin/channel/hub/envelopes.ts +66 -0
- package/plugin/channel/hub/lifecycle.ts +91 -0
- package/plugin/channel/hub/local-roads.ts +89 -0
- package/plugin/channel/hub/remote-roads.ts +165 -0
- package/plugin/channel/hub/road-controller.ts +99 -0
- package/plugin/channel/hub-client.ts +149 -0
- package/plugin/channel/local-hub.js +5995 -0
- package/plugin/channel/local-hub.ts +349 -0
- package/plugin/channel/map-reporter.ts +48 -0
- package/plugin/channel/package-lock.json +1731 -0
- package/plugin/channel/package.json +19 -0
- package/plugin/channel/protocol.ts +98 -0
- package/plugin/channel/road-cli.ts +18 -0
- package/plugin/channel/run.sh +15 -0
- package/plugin/channel/runtime/claude.ts +458 -0
- package/plugin/channel/runtime/codex-config.ts +77 -0
- package/plugin/channel/runtime/codex.ts +669 -0
- package/plugin/channel/runtime/command.ts +62 -0
- package/plugin/channel/runtime/factory.ts +15 -0
- package/plugin/channel/runtime/json-rpc.ts +154 -0
- package/plugin/channel/runtime/kimi.ts +277 -0
- package/plugin/channel/runtime/opencode.ts +248 -0
- package/plugin/channel/runtime/process.ts +84 -0
- package/plugin/channel/runtime/types.ts +45 -0
- package/plugin/channel/runtime-files.ts +73 -0
- package/plugin/channel/runtime-gateway.js +6396 -0
- package/plugin/channel/runtime-gateway.ts +358 -0
- package/plugin/channel/runtime-metrics.ts +52 -0
- package/plugin/channel/runtime-subscription.ts +180 -0
- package/plugin/channel/terminal-delivery.ts +131 -0
- package/plugin/channel/untrusted.ts +60 -0
- package/plugin/commands/committee.md +31 -0
- package/plugin/commands/exit.md +32 -0
- package/plugin/commands/goals.md +20 -0
- package/plugin/commands/join.md +18 -0
- package/plugin/commands/notice.md +20 -0
- package/plugin/commands/propose.md +19 -0
- package/plugin/commands/round.md +17 -0
- package/plugin/commands/session.md +22 -0
- package/plugin/commands/settings.md +25 -0
- package/plugin/commands/setup.md +21 -0
- package/plugin/commands/team.md +23 -0
- package/plugin/domains/custom.md +27 -0
- package/plugin/domains/finance.md +31 -0
- package/plugin/domains/healthcare.md +34 -0
- package/plugin/domains/legal.md +31 -0
- package/plugin/domains/marketing.md +33 -0
- package/plugin/domains/operations.md +31 -0
- package/plugin/domains/research.md +32 -0
- package/plugin/domains/sales.md +30 -0
- package/plugin/domains/software.md +37 -0
- package/plugin/hooks/activity.sh +6 -0
- package/plugin/hooks/digging.sh +68 -0
- package/plugin/hooks/growth.sh +42 -0
- package/plugin/hooks/hooks.json +106 -0
- package/plugin/hooks/notice-on-pr.sh +35 -0
- package/plugin/hooks/notice-on-stop.sh +80 -0
- package/plugin/hooks/notice-pending.sh +58 -0
- package/plugin/hooks/solo-en-ciudad.sh +23 -0
- package/plugin/hooks/tokens.sh +47 -0
- package/plugin/roles/examples/account-executive.md +25 -0
- package/plugin/roles/examples/ai-manager.md +34 -0
- package/plugin/roles/examples/associate.md +24 -0
- package/plugin/roles/examples/brand-lead.md +20 -0
- package/plugin/roles/examples/cfo.md +20 -0
- package/plugin/roles/examples/city-lead.md +25 -0
- package/plugin/roles/examples/clinical-director.md +26 -0
- package/plugin/roles/examples/clinical-ops.md +25 -0
- package/plugin/roles/examples/clinician.md +25 -0
- package/plugin/roles/examples/compliance.md +27 -0
- package/plugin/roles/examples/content.md +24 -0
- package/plugin/roles/examples/controller.md +27 -0
- package/plugin/roles/examples/cpto.md +41 -0
- package/plugin/roles/examples/customer-success.md +25 -0
- package/plugin/roles/examples/data-engineer.md +37 -0
- package/plugin/roles/examples/data.md +60 -0
- package/plugin/roles/examples/dev.md +37 -0
- package/plugin/roles/examples/devops.md +39 -0
- package/plugin/roles/examples/enablement.md +24 -0
- package/plugin/roles/examples/ethics.md +24 -0
- package/plugin/roles/examples/fin-analytics.md +27 -0
- package/plugin/roles/examples/health-compliance.md +25 -0
- package/plugin/roles/examples/health-data.md +26 -0
- package/plugin/roles/examples/knowledge.md +25 -0
- package/plugin/roles/examples/lifecycle.md +26 -0
- package/plugin/roles/examples/llm-engineer.md +34 -0
- package/plugin/roles/examples/managing-partner.md +20 -0
- package/plugin/roles/examples/methods.md +23 -0
- package/plugin/roles/examples/operations-lead.md +23 -0
- package/plugin/roles/examples/ops.md +24 -0
- package/plugin/roles/examples/patient-safety.md +26 -0
- package/plugin/roles/examples/performance.md +27 -0
- package/plugin/roles/examples/po.md +36 -0
- package/plugin/roles/examples/process-owner.md +23 -0
- package/plugin/roles/examples/product-design.md +39 -0
- package/plugin/roles/examples/program-manager.md +24 -0
- package/plugin/roles/examples/quality.md +25 -0
- package/plugin/roles/examples/research-director.md +24 -0
- package/plugin/roles/examples/research-ops.md +25 -0
- package/plugin/roles/examples/researcher.md +25 -0
- package/plugin/roles/examples/revenue-lead.md +25 -0
- package/plugin/roles/examples/revops.md +26 -0
- package/plugin/roles/examples/seo.md +27 -0
- package/plugin/roles/examples/specialist.md +24 -0
- package/plugin/scripts/actualiza.py +198 -0
- package/plugin/scripts/admision.py +165 -0
- package/plugin/scripts/apaga.py +232 -0
- package/plugin/scripts/atajos.py +506 -0
- package/plugin/scripts/avatar.py +211 -0
- package/plugin/scripts/broker.py +531 -0
- package/plugin/scripts/cage.py +492 -0
- package/plugin/scripts/capabilities.py +210 -0
- package/plugin/scripts/card.py +322 -0
- package/plugin/scripts/cities.py +520 -0
- package/plugin/scripts/city-env.sh +90 -0
- package/plugin/scripts/city-runtime.sh +87 -0
- package/plugin/scripts/city-session.sh +552 -0
- package/plugin/scripts/city_env.py +67 -0
- package/plugin/scripts/crecimiento.py +140 -0
- package/plugin/scripts/deliberations.py +48 -0
- package/plugin/scripts/doctor.py +294 -0
- package/plugin/scripts/domains.py +201 -0
- package/plugin/scripts/evidencia.py +64 -0
- package/plugin/scripts/find-repos.sh +111 -0
- package/plugin/scripts/gh.py +159 -0
- package/plugin/scripts/hall_protocol.py +147 -0
- package/plugin/scripts/hook_activity.py +139 -0
- package/plugin/scripts/launch.py +94 -0
- package/plugin/scripts/logs.py +98 -0
- package/plugin/scripts/pairing.py +185 -0
- package/plugin/scripts/parcels.py +132 -0
- package/plugin/scripts/read-card.py +55 -0
- package/plugin/scripts/report.py +201 -0
- package/plugin/scripts/reset.py +180 -0
- package/plugin/scripts/roads.py +230 -0
- package/plugin/scripts/roles.py +274 -0
- package/plugin/scripts/runtime_log.py +69 -0
- package/plugin/scripts/runtime_processes.py +132 -0
- package/plugin/scripts/rutas.py +83 -0
- package/plugin/scripts/seat.py +1696 -0
- package/plugin/scripts/tokens.py +194 -0
- package/plugin/scripts/trust-repos.py +59 -0
- package/plugin/scripts/ui.py +280 -0
- package/plugin/scripts/units.py +80 -0
- package/plugin/scripts/workspace.py +561 -0
- package/plugin/skills/city/SKILL.md +191 -0
- package/templates/blank.md +39 -0
- package/templates/finance.md +44 -0
- package/templates/legal.md +45 -0
- package/templates/marketing.md +48 -0
- package/templates/product.md +45 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The roles offered to an owner seat, across the built-in work domains.
|
|
3
|
+
|
|
4
|
+
The v2 road address is stable ``owner/city`` and never depends on role. The short
|
|
5
|
+
role suffixes remain only to read and import v1 cards such as ``alice/lead``.
|
|
6
|
+
|
|
7
|
+
The catalogue is shared by the Hall and seat so they cannot label the same role
|
|
8
|
+
differently. Which subset is relevant is owned by ``domains.py``: a city first
|
|
9
|
+
chooses its work domain, then one role inside it. Role descriptions may still be
|
|
10
|
+
overridden by a city's own role files.
|
|
11
|
+
|
|
12
|
+
Only picker labels and one-line summaries live here. The operating knowledge and
|
|
13
|
+
trade come from each role's Markdown file, looked up in your city first and the
|
|
14
|
+
plugin's examples second — duplicating those in code is how a managing partner
|
|
15
|
+
once got labelled a master builder.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
AQUI = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
|
|
23
|
+
# (id, what the city calls it, its one line, whether it owns a property of every
|
|
24
|
+
# folder rather than folders of its own).
|
|
25
|
+
CATALOGO = [
|
|
26
|
+
# Deliberately no role pack. ``blank`` is an explicit choice to start with
|
|
27
|
+
# no predefined responsibility or operating knowledge; it is offered in
|
|
28
|
+
# every domain and can also be assigned to an individual repo agent.
|
|
29
|
+
("blank", "Blank role", "no predefined responsibility or role knowledge", True),
|
|
30
|
+
# Software development
|
|
31
|
+
("cpto", "Product & technology lead", "direction, priorities and product outcomes", True),
|
|
32
|
+
("dev", "Software engineer", "owns repositories and answers for their code", False),
|
|
33
|
+
(
|
|
34
|
+
"data-engineer",
|
|
35
|
+
"Data engineer",
|
|
36
|
+
"pipelines, transformations, storage contracts and data reliability",
|
|
37
|
+
False,
|
|
38
|
+
),
|
|
39
|
+
("devops", "Platform / DevOps lead", "security, reliability, performance, cost and CI", True),
|
|
40
|
+
("data", "Data & analytics lead", "events, schemas and comparable measurement", True),
|
|
41
|
+
("product-design", "Product designer", "design system, accessibility and user flows", True),
|
|
42
|
+
("po", "Product manager", "scope, priority and delivery coherence", True),
|
|
43
|
+
("llm-engineer", "AI / LLM engineer", "model calls, prompts, evaluations and token cost", True),
|
|
44
|
+
("ai-manager", "AI portfolio lead", "initiative overlap, adoption and portfolio value", True),
|
|
45
|
+
# Marketing, legal and finance
|
|
46
|
+
("brand-lead", "Brand lead", "positioning, priorities and brand coherence", True),
|
|
47
|
+
("content", "Content lead", "owns pieces, channels and editorial intent", False),
|
|
48
|
+
("performance", "Performance marketing lead", "paid acquisition, spend and conversion", True),
|
|
49
|
+
("seo", "SEO lead", "organic structure, indexing, links and durable URLs", True),
|
|
50
|
+
(
|
|
51
|
+
"lifecycle",
|
|
52
|
+
"Lifecycle / CRM lead",
|
|
53
|
+
"segments, triggers, sequences and contact pressure",
|
|
54
|
+
True,
|
|
55
|
+
),
|
|
56
|
+
("managing-partner", "Managing partner", "practice direction, matters and staffing", True),
|
|
57
|
+
("associate", "Lawyer / associate", "owns matters and their legal context", False),
|
|
58
|
+
("compliance", "Compliance lead", "obligations, deadlines, conflicts and records", True),
|
|
59
|
+
("knowledge", "Knowledge lead", "precedents, reusable work and institutional memory", True),
|
|
60
|
+
("cfo", "CFO / finance lead", "financial direction, priorities and resource choices", True),
|
|
61
|
+
("controller", "Controller", "close, reconciliations, controls and audit trail", True),
|
|
62
|
+
(
|
|
63
|
+
"fin-analytics",
|
|
64
|
+
"Financial analyst",
|
|
65
|
+
"definitions, comparability, forecasts and scenarios",
|
|
66
|
+
True,
|
|
67
|
+
),
|
|
68
|
+
("ops", "Operations specialist", "owns operational processes and their exceptions", False),
|
|
69
|
+
# Healthcare
|
|
70
|
+
(
|
|
71
|
+
"clinical-director",
|
|
72
|
+
"Clinical director",
|
|
73
|
+
"clinical direction, accountability and care priorities",
|
|
74
|
+
True,
|
|
75
|
+
),
|
|
76
|
+
("clinician", "Clinician", "owns patient or pathway context and clinical judgement", False),
|
|
77
|
+
(
|
|
78
|
+
"patient-safety",
|
|
79
|
+
"Patient safety & quality lead",
|
|
80
|
+
"harm prevention, incidents and safeguards",
|
|
81
|
+
True,
|
|
82
|
+
),
|
|
83
|
+
(
|
|
84
|
+
"clinical-ops",
|
|
85
|
+
"Clinical operations lead",
|
|
86
|
+
"capacity, pathways, handoffs and service delivery",
|
|
87
|
+
True,
|
|
88
|
+
),
|
|
89
|
+
("health-data", "Clinical data lead", "health measures, provenance and data quality", True),
|
|
90
|
+
(
|
|
91
|
+
"health-compliance",
|
|
92
|
+
"Health privacy & compliance lead",
|
|
93
|
+
"privacy, consent, access and regulation",
|
|
94
|
+
True,
|
|
95
|
+
),
|
|
96
|
+
# Revenue
|
|
97
|
+
("revenue-lead", "Revenue leader", "commercial direction, forecast and commitments", True),
|
|
98
|
+
("account-executive", "Account executive", "owns opportunities and buyer context", False),
|
|
99
|
+
(
|
|
100
|
+
"revops",
|
|
101
|
+
"Revenue operations lead",
|
|
102
|
+
"pipeline definitions, routing, systems and forecast hygiene",
|
|
103
|
+
True,
|
|
104
|
+
),
|
|
105
|
+
(
|
|
106
|
+
"customer-success",
|
|
107
|
+
"Customer success lead",
|
|
108
|
+
"adoption, outcomes, renewal and churn risk",
|
|
109
|
+
True,
|
|
110
|
+
),
|
|
111
|
+
(
|
|
112
|
+
"enablement",
|
|
113
|
+
"Sales enablement lead",
|
|
114
|
+
"playbooks, evidence and reusable field knowledge",
|
|
115
|
+
True,
|
|
116
|
+
),
|
|
117
|
+
# Research and general operations
|
|
118
|
+
(
|
|
119
|
+
"research-director",
|
|
120
|
+
"Research director",
|
|
121
|
+
"questions, portfolio direction and research decisions",
|
|
122
|
+
True,
|
|
123
|
+
),
|
|
124
|
+
("researcher", "Researcher", "owns studies, sources and interpretation", False),
|
|
125
|
+
(
|
|
126
|
+
"methods",
|
|
127
|
+
"Methods / statistics lead",
|
|
128
|
+
"study design, validity, uncertainty and analysis",
|
|
129
|
+
True,
|
|
130
|
+
),
|
|
131
|
+
(
|
|
132
|
+
"research-ops",
|
|
133
|
+
"Research operations lead",
|
|
134
|
+
"participants, tooling, data and study delivery",
|
|
135
|
+
True,
|
|
136
|
+
),
|
|
137
|
+
("ethics", "Ethics lead", "consent, proportionality, vulnerable groups and review", True),
|
|
138
|
+
("operations-lead", "Operations lead", "operating direction, outcomes and trade-offs", True),
|
|
139
|
+
("program-manager", "Programme manager", "dependencies, scope, milestones and delivery", True),
|
|
140
|
+
("process-owner", "Process owner", "owns a process, its exceptions and its outcomes", False),
|
|
141
|
+
(
|
|
142
|
+
"quality",
|
|
143
|
+
"Quality & risk lead",
|
|
144
|
+
"acceptance, failure modes, controls and verification",
|
|
145
|
+
True,
|
|
146
|
+
),
|
|
147
|
+
("city-lead", "City lead", "sets direction and chairs decisions in a custom domain", True),
|
|
148
|
+
("specialist", "Domain specialist", "owns a body of work and answers from its evidence", False),
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
# Short, lowercase and stable. A role nobody anticipated still gets a sensible name
|
|
152
|
+
# from `sufijo()` rather than nothing.
|
|
153
|
+
SUFIJOS = {
|
|
154
|
+
"blank": "blank",
|
|
155
|
+
"cpto": "lead",
|
|
156
|
+
"devops": "ops",
|
|
157
|
+
"data": "data",
|
|
158
|
+
"product-design": "design",
|
|
159
|
+
"po": "po",
|
|
160
|
+
"llm-engineer": "llm",
|
|
161
|
+
"ai-manager": "ai",
|
|
162
|
+
"dev": "dev",
|
|
163
|
+
"data-engineer": "dataeng",
|
|
164
|
+
"brand-lead": "brand",
|
|
165
|
+
"content": "content",
|
|
166
|
+
"performance": "ads",
|
|
167
|
+
"seo": "seo",
|
|
168
|
+
"lifecycle": "crm",
|
|
169
|
+
"managing-partner": "partner",
|
|
170
|
+
"associate": "assoc",
|
|
171
|
+
"compliance": "comp",
|
|
172
|
+
"knowledge": "know",
|
|
173
|
+
"cfo": "cfo",
|
|
174
|
+
"controller": "ctrl",
|
|
175
|
+
"fin-analytics": "fin",
|
|
176
|
+
"ops": "ops",
|
|
177
|
+
"clinical-director": "clinical",
|
|
178
|
+
"clinician": "clinician",
|
|
179
|
+
"patient-safety": "safety",
|
|
180
|
+
"clinical-ops": "careops",
|
|
181
|
+
"health-data": "healthdata",
|
|
182
|
+
"health-compliance": "privacy",
|
|
183
|
+
"revenue-lead": "revenue",
|
|
184
|
+
"account-executive": "account",
|
|
185
|
+
"revops": "revops",
|
|
186
|
+
"customer-success": "success",
|
|
187
|
+
"enablement": "enable",
|
|
188
|
+
"research-director": "research",
|
|
189
|
+
"researcher": "researcher",
|
|
190
|
+
"methods": "methods",
|
|
191
|
+
"research-ops": "resops",
|
|
192
|
+
"ethics": "ethics",
|
|
193
|
+
"operations-lead": "operations",
|
|
194
|
+
"program-manager": "program",
|
|
195
|
+
"process-owner": "process",
|
|
196
|
+
"quality": "quality",
|
|
197
|
+
"city-lead": "lead",
|
|
198
|
+
"specialist": "specialist",
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# Whoever sets the goals. Not a style rule: goals are the one thing this system keeps
|
|
202
|
+
# whatever kind of city you build, and they are agreed between the architect and each
|
|
203
|
+
# city seat. Without that seat there is nobody to agree them with, and every round
|
|
204
|
+
# degrades into a status report.
|
|
205
|
+
ARQUITECTOS = {
|
|
206
|
+
"cpto",
|
|
207
|
+
"brand-lead",
|
|
208
|
+
"managing-partner",
|
|
209
|
+
"cfo",
|
|
210
|
+
"clinical-director",
|
|
211
|
+
"revenue-lead",
|
|
212
|
+
"research-director",
|
|
213
|
+
"operations-lead",
|
|
214
|
+
"city-lead",
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def sufijo(rol):
|
|
219
|
+
"""The agent-name suffix for a role."""
|
|
220
|
+
return SUFIJOS.get(rol, rol.split("-")[0][:8])
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def agente(usuario, rol, ciudad=""):
|
|
224
|
+
"""A stable owner/city address, or the legacy user/role address.
|
|
225
|
+
|
|
226
|
+
Repo agents stay private behind the seat. Once a city is known, the public
|
|
227
|
+
endpoint is ``user/city`` so changing its role cannot break every road.
|
|
228
|
+
"""
|
|
229
|
+
if ciudad:
|
|
230
|
+
segura = re.sub(r"[^a-z0-9-]+", "-", ciudad.lower()).strip("-") or "city"
|
|
231
|
+
return f"{usuario}/{segura}"
|
|
232
|
+
return f"{usuario}/{sufijo(rol)}"
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def nombre(rol):
|
|
236
|
+
"""What the city calls this role, or a readable version of its id."""
|
|
237
|
+
for r, n, _, _ in CATALOGO:
|
|
238
|
+
if r == rol:
|
|
239
|
+
return n
|
|
240
|
+
return rol.replace("-", " ").title()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def oficio(rol, datos=""):
|
|
244
|
+
"""The trade, read from the role's own file — never from a table in code.
|
|
245
|
+
|
|
246
|
+
A hardcoded map labels every unknown role as the same thing, which is exactly
|
|
247
|
+
what once turned a managing partner into a master builder. Looked up in the data
|
|
248
|
+
repo first (roles are data, and a team renames them), then in the examples this
|
|
249
|
+
plugin ships. `../roles/examples` and not a path through the clone, because the
|
|
250
|
+
installed plugin has no clone — this is the one lookup that works at both doors.
|
|
251
|
+
|
|
252
|
+
There were two copies of this: the wizard's skipped the data repo, so a re-run
|
|
253
|
+
against renamed roles showed the stale trade. One copy, the complete one.
|
|
254
|
+
"""
|
|
255
|
+
if rol == "blank":
|
|
256
|
+
return "No preset"
|
|
257
|
+
bases = [os.path.join(datos, "roles")] if datos else []
|
|
258
|
+
bases.append(os.path.join(AQUI, "..", "roles", "examples"))
|
|
259
|
+
for base in bases:
|
|
260
|
+
f = os.path.join(base, f"{rol}.md")
|
|
261
|
+
if os.path.exists(f):
|
|
262
|
+
m = re.search(r"^trade:\s*(.+)$", open(f).read(), re.M)
|
|
263
|
+
if m:
|
|
264
|
+
return m.group(1).strip()
|
|
265
|
+
# No role file: say what the role is called rather than inventing a trade.
|
|
266
|
+
return rol.replace("-", " ").title()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def transversal(rol):
|
|
270
|
+
"""Whether this role owns a property of every folder rather than folders."""
|
|
271
|
+
for r, _, _, t in CATALOGO:
|
|
272
|
+
if r == rol:
|
|
273
|
+
return t
|
|
274
|
+
return True
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Append secret-scrubbed operational diagnostics for one local city."""
|
|
3
|
+
import argparse
|
|
4
|
+
import datetime
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
import runtime_processes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _clean(value, limit=2000):
|
|
14
|
+
text = str(value or '').strip()
|
|
15
|
+
text = re.sub(r'([?&](?:token|secret|key)=)[^&\s]+', r'\1[redacted]', text,
|
|
16
|
+
flags=re.IGNORECASE)
|
|
17
|
+
text = re.sub(r'Bearer\s+\S+', 'Bearer [redacted]', text, flags=re.IGNORECASE)
|
|
18
|
+
text = re.sub(r'(Authorization:\s*)\S+(?:\s+\S+)?', r'\1[redacted]', text,
|
|
19
|
+
flags=re.IGNORECASE)
|
|
20
|
+
text = re.sub(r'(--(?:token|password|secret|api-key)(?:=|\s+))\S+',
|
|
21
|
+
r'\1[redacted]', text, flags=re.IGNORECASE)
|
|
22
|
+
text = re.sub(r'((?:TOKEN|SECRET|PASSWORD|API_KEY)=)[^\s]+',
|
|
23
|
+
r'\1[redacted]', text, flags=re.IGNORECASE)
|
|
24
|
+
return text[:limit]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def append(data, component, event, **fields):
|
|
28
|
+
"""Best-effort logging: diagnostics must never stop an agent."""
|
|
29
|
+
try:
|
|
30
|
+
directory = runtime_processes.ruta(data)
|
|
31
|
+
os.makedirs(directory, mode=0o700, exist_ok=True)
|
|
32
|
+
path = os.path.join(directory, 'diagnostics.jsonl')
|
|
33
|
+
record = {
|
|
34
|
+
'protocol': 'agents-city-diagnostic/1',
|
|
35
|
+
'id': 'diagnostic_' + uuid.uuid4().hex,
|
|
36
|
+
'at': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
37
|
+
'pid': os.getpid(),
|
|
38
|
+
'component': _clean(component, 80),
|
|
39
|
+
'event': _clean(event, 120),
|
|
40
|
+
}
|
|
41
|
+
for key, value in fields.items():
|
|
42
|
+
if value is None:
|
|
43
|
+
continue
|
|
44
|
+
record[key] = ('[redacted]' if re.search(
|
|
45
|
+
r'token|secret|authorization|credential', key, re.IGNORECASE)
|
|
46
|
+
else _clean(value))
|
|
47
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
|
|
48
|
+
with os.fdopen(fd, 'a', encoding='utf-8') as stream:
|
|
49
|
+
stream.write(json.dumps(record, ensure_ascii=False) + '\n')
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
parser = argparse.ArgumentParser()
|
|
56
|
+
parser.add_argument('event')
|
|
57
|
+
parser.add_argument('--data', default=os.environ.get('AGENTS_CITY_DATA', ''))
|
|
58
|
+
parser.add_argument('--component', default='launcher')
|
|
59
|
+
parser.add_argument('--actor', default=os.environ.get('CITY_BUS_ACTOR', ''))
|
|
60
|
+
parser.add_argument('--outcome', default='')
|
|
61
|
+
parser.add_argument('--message', default='')
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
if args.data:
|
|
64
|
+
append(args.data, args.component, args.event, actor=args.actor,
|
|
65
|
+
outcome=args.outcome, message=args.message)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == '__main__':
|
|
69
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Exact process markers for one city's local bus and runtime gateways."""
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shlex
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
import cities
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ruta(datos):
|
|
13
|
+
ident = cities.identidad(datos)
|
|
14
|
+
key = re.sub(r'[^a-z0-9-]+', '-', ident.lower()).strip('-')[:80] or 'city'
|
|
15
|
+
return os.path.join(cities.raiz(), '.runtime', 'bus', key)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def procesos(datos):
|
|
19
|
+
"""Return every exact city runtime, including one that lost its marker.
|
|
20
|
+
|
|
21
|
+
Marker files are the fast path. A crashed or racing predecessor can lose
|
|
22
|
+
its endpoint while the process survives, so also inspect argv and accept
|
|
23
|
+
only a known entrypoint whose literal ``--data`` resolves to this city.
|
|
24
|
+
"""
|
|
25
|
+
datos = os.path.realpath(datos)
|
|
26
|
+
base = ruta(datos)
|
|
27
|
+
candidatos = []
|
|
28
|
+
try:
|
|
29
|
+
endpoint = json.load(open(os.path.join(base, 'endpoint.json'), encoding='utf-8'))
|
|
30
|
+
candidatos.append(('bus hub', int(endpoint.get('pid', 0)), 'local-hub.js'))
|
|
31
|
+
except (OSError, ValueError, TypeError, json.JSONDecodeError):
|
|
32
|
+
pass
|
|
33
|
+
for folder, label, marker in (
|
|
34
|
+
('gateways', 'gateway', 'runtime-gateway.js'),
|
|
35
|
+
('adapters', 'terminal fallback', 'adapter.js')):
|
|
36
|
+
directory = os.path.join(base, folder)
|
|
37
|
+
try:
|
|
38
|
+
nombres = [n for n in os.listdir(directory) if n.endswith('.pid')]
|
|
39
|
+
except OSError:
|
|
40
|
+
nombres = []
|
|
41
|
+
for nombre in nombres:
|
|
42
|
+
try:
|
|
43
|
+
pid = int(open(os.path.join(directory, nombre), encoding='utf-8').read().strip())
|
|
44
|
+
except (OSError, ValueError):
|
|
45
|
+
continue
|
|
46
|
+
candidatos.append((f'{label} {nombre[:-4]}', pid, marker))
|
|
47
|
+
|
|
48
|
+
vistos = set()
|
|
49
|
+
fuera = []
|
|
50
|
+
for etiqueta, pid, componente in candidatos:
|
|
51
|
+
if pid <= 0:
|
|
52
|
+
continue
|
|
53
|
+
try:
|
|
54
|
+
comando = subprocess.run(
|
|
55
|
+
['ps', '-p', str(pid), '-o', 'command='],
|
|
56
|
+
capture_output=True, text=True, timeout=3).stdout.strip()
|
|
57
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
58
|
+
comando = ''
|
|
59
|
+
if componente in comando and datos in comando:
|
|
60
|
+
fuera.append({'label': etiqueta, 'pid': pid})
|
|
61
|
+
vistos.add(pid)
|
|
62
|
+
for proceso in _procesos_sin_marca(datos):
|
|
63
|
+
if proceso['pid'] not in vistos:
|
|
64
|
+
fuera.append(proceso)
|
|
65
|
+
vistos.add(proceso['pid'])
|
|
66
|
+
return fuera
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _procesos_sin_marca(datos):
|
|
70
|
+
"""Discover only node entrypoints with an exact ``--data <city>`` argv."""
|
|
71
|
+
try:
|
|
72
|
+
salida = subprocess.run(
|
|
73
|
+
['ps', '-Ao', 'pid=,command='], capture_output=True, text=True,
|
|
74
|
+
timeout=5).stdout
|
|
75
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
76
|
+
return []
|
|
77
|
+
componentes = {
|
|
78
|
+
'local-hub.js': 'bus hub',
|
|
79
|
+
'runtime-gateway.js': 'gateway',
|
|
80
|
+
'adapter.js': 'terminal fallback',
|
|
81
|
+
}
|
|
82
|
+
encontrados = []
|
|
83
|
+
for linea in salida.splitlines():
|
|
84
|
+
match = re.match(r'\s*(\d+)\s+(.*)', linea)
|
|
85
|
+
if not match:
|
|
86
|
+
continue
|
|
87
|
+
pid, comando = int(match.group(1)), match.group(2)
|
|
88
|
+
if pid in (os.getpid(), os.getppid()):
|
|
89
|
+
continue
|
|
90
|
+
try:
|
|
91
|
+
argv = shlex.split(comando)
|
|
92
|
+
except ValueError:
|
|
93
|
+
continue
|
|
94
|
+
indice = next(
|
|
95
|
+
(i for i, value in enumerate(argv[:3])
|
|
96
|
+
if os.path.basename(value) in componentes), None)
|
|
97
|
+
if indice is None:
|
|
98
|
+
continue
|
|
99
|
+
try:
|
|
100
|
+
data_index = argv.index('--data', indice + 1)
|
|
101
|
+
data = os.path.realpath(argv[data_index + 1])
|
|
102
|
+
except (ValueError, IndexError):
|
|
103
|
+
continue
|
|
104
|
+
if data != datos:
|
|
105
|
+
continue
|
|
106
|
+
componente = os.path.basename(argv[indice])
|
|
107
|
+
etiqueta = componentes[componente]
|
|
108
|
+
if componente in ('runtime-gateway.js', 'adapter.js'):
|
|
109
|
+
try:
|
|
110
|
+
actor = argv[argv.index('--actor', indice + 1) + 1]
|
|
111
|
+
etiqueta = f'{etiqueta} {actor}'
|
|
112
|
+
except (ValueError, IndexError):
|
|
113
|
+
pass
|
|
114
|
+
encontrados.append({'label': etiqueta, 'pid': pid})
|
|
115
|
+
return encontrados
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def limpia_marcas(datos):
|
|
119
|
+
base = ruta(datos)
|
|
120
|
+
rutas = [os.path.join(base, 'endpoint.json'), os.path.join(base, 'hub.lock')]
|
|
121
|
+
for folder in ('gateways', 'adapters'):
|
|
122
|
+
directory = os.path.join(base, folder)
|
|
123
|
+
try:
|
|
124
|
+
rutas += [os.path.join(directory, n) for n in os.listdir(directory)
|
|
125
|
+
if n.endswith('.pid')]
|
|
126
|
+
except OSError:
|
|
127
|
+
pass
|
|
128
|
+
for path in rutas:
|
|
129
|
+
try:
|
|
130
|
+
os.unlink(path)
|
|
131
|
+
except OSError:
|
|
132
|
+
pass
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Path containment, resolved twice, in one place.
|
|
3
|
+
|
|
4
|
+
Every part of the security layer that trusts a path — the cage deciding what to
|
|
5
|
+
re-allow for writing, the broker deciding which repo a token may act on — needs
|
|
6
|
+
the same three questions answered the same way:
|
|
7
|
+
|
|
8
|
+
1. Where does this path *really* point, even if its leaf does not exist yet?
|
|
9
|
+
A non-existent leaf under a symlinked parent still redirects, so we resolve
|
|
10
|
+
through the deepest ancestor that does exist.
|
|
11
|
+
2. Is it inside a forbidden root?
|
|
12
|
+
3. Does it *cover* a forbidden root (an ancestor of it)? Re-allowing `~` would
|
|
13
|
+
silently re-open `~/.ssh`; that is the bug this module exists to refuse.
|
|
14
|
+
|
|
15
|
+
Kept dependency-free and pure so both the cage and the broker import it, and so
|
|
16
|
+
the tests can drive it on any platform without a sandbox.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def canonicaliza(ruta):
|
|
23
|
+
"""Absolute real path, resolved through the deepest ancestor that exists.
|
|
24
|
+
|
|
25
|
+
`os.path.realpath` already does this on most inputs, but it stops resolving
|
|
26
|
+
once it meets a missing component; we walk up to the first existing ancestor,
|
|
27
|
+
resolve *that*, then re-attach the missing tail. A symlinked parent with a
|
|
28
|
+
not-yet-created child therefore still lands on the real target.
|
|
29
|
+
"""
|
|
30
|
+
ruta = os.path.abspath(os.path.expanduser(ruta))
|
|
31
|
+
cola = []
|
|
32
|
+
actual = ruta
|
|
33
|
+
while not os.path.exists(actual):
|
|
34
|
+
padre = os.path.dirname(actual)
|
|
35
|
+
if padre == actual:
|
|
36
|
+
break
|
|
37
|
+
cola.append(os.path.basename(actual))
|
|
38
|
+
actual = padre
|
|
39
|
+
base = os.path.realpath(actual)
|
|
40
|
+
for segmento in reversed(cola):
|
|
41
|
+
base = os.path.join(base, segmento)
|
|
42
|
+
return base
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _segmentos(ruta):
|
|
46
|
+
return [s for s in canonicaliza(ruta).split(os.sep) if s]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _dentro_seg(h, p):
|
|
50
|
+
return len(h) >= len(p) and h[: len(p)] == p
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _cubre_seg(a, d):
|
|
54
|
+
return len(a) < len(d) and d[: len(a)] == a
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def dentro_de(hijo, padre):
|
|
58
|
+
"""True when `hijo` is `padre` or lives under it — compared segment-wise so
|
|
59
|
+
`/a/bc` is never judged to be inside `/a/b`."""
|
|
60
|
+
return _dentro_seg(_segmentos(hijo), _segmentos(padre))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cubre(ancestro, descendiente):
|
|
64
|
+
"""True when `ancestro` is a strict parent of `descendiente` (covers it)."""
|
|
65
|
+
return _cubre_seg(_segmentos(ancestro), _segmentos(descendiente))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def motivo_bloqueo(ruta, bloqueados):
|
|
69
|
+
"""Why `ruta` may not be trusted against a set of forbidden roots, or None.
|
|
70
|
+
|
|
71
|
+
Bidirectional on purpose: a path is refused both when it sits inside a
|
|
72
|
+
forbidden root and when it is an ancestor that would re-open one. `ruta` is
|
|
73
|
+
canonicalized once here and compared segment-wise against every root.
|
|
74
|
+
"""
|
|
75
|
+
r_can = canonicaliza(ruta)
|
|
76
|
+
r_seg = [s for s in r_can.split(os.sep) if s]
|
|
77
|
+
for raiz in bloqueados:
|
|
78
|
+
raiz_seg = _segmentos(raiz)
|
|
79
|
+
if _dentro_seg(r_seg, raiz_seg):
|
|
80
|
+
return f'{r_can} is inside sealed {canonicaliza(raiz)}'
|
|
81
|
+
if _cubre_seg(r_seg, raiz_seg):
|
|
82
|
+
return f'{r_can} covers sealed {canonicaliza(raiz)}'
|
|
83
|
+
return None
|