agents-city 0.4.0 → 0.5.2
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 +1 -1
- package/README.es.md +240 -11
- package/README.md +238 -11
- package/benchmarks/latency/fake-claude-cli.mjs +2 -1
- package/benchmarks/reception/README.md +16 -0
- package/benchmarks/reception/run.mjs +84 -0
- package/bin/agents-city.js +1 -0
- package/bin/connect +5 -0
- package/bin/hall.html +117 -18
- package/bin/navegador.mjs +170 -1
- package/bin/serve.py +201 -101
- package/bin/test +15 -3
- package/bin/test-arnes.py +192 -0
- package/bin/test-cage.py +9 -0
- package/bin/test-channel.py +178 -12
- package/bin/test-claude-runtime.py +132 -4
- package/bin/test-connect-client.mjs +609 -0
- package/bin/test-connect.py +52 -0
- package/bin/test-contracts.py +214 -0
- package/bin/test-diario.py +185 -0
- package/bin/test-doctor.py +31 -0
- package/bin/test-i18n.py +189 -0
- package/bin/test-navegador.py +61 -0
- package/bin/test-seat.py +78 -20
- package/bin/test-security.py +2 -0
- package/bin/test-serve.py +366 -0
- package/city/web/dist-hall/hall.js +789 -214
- package/city/web/src/bienvenida.ts +14 -13
- package/city/web/src/casa.ts +143 -56
- package/city/web/src/demo.ts +61 -31
- package/city/web/src/es.ts +187 -27
- package/city/web/src/explorador.ts +70 -43
- package/city/web/src/hall.ts +520 -103
- package/city/web/src/idioma.ts +21 -1
- package/city/web/src/motores.ts +48 -1
- package/city/web/src/vista.ts +58 -0
- package/demo/graba.py +5 -10
- package/docs/managed-connect.md +237 -0
- package/docs/security.md +41 -2
- package/package.json +8 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/channel/adapter-prompts.ts +10 -0
- package/plugin/channel/adapter.js +78 -31
- package/plugin/channel/agents_city_hybrid_crypto_bg.wasm +0 -0
- package/plugin/channel/bus.js +109 -65
- package/plugin/channel/bus.ts +11 -1
- package/plugin/channel/client.js +67 -30
- package/plugin/channel/delivery-queue.ts +160 -21
- package/plugin/channel/hub/remote-roads.ts +32 -1
- package/plugin/channel/hub/road-controller.ts +116 -16
- package/plugin/channel/kinsh_vodozemac_wasm_bg.wasm +0 -0
- package/plugin/channel/licenses/CONNECT_CLIENT_THIRD_PARTY_NOTICES.md +21 -0
- package/plugin/channel/licenses/HYBRID_CRYPTO_THIRD_PARTY_NOTICES.md +12 -0
- package/plugin/channel/licenses/hybrid-crypto-Apache-2.0.txt +201 -0
- package/plugin/channel/licenses/keyring-MIT.txt +21 -0
- package/plugin/channel/licenses/vodozemac-Apache-2.0.txt +201 -0
- package/plugin/channel/local-hub.js +2080 -97
- package/plugin/channel/local-hub.ts +5 -4
- package/plugin/channel/managed-connect/bridge.ts +208 -0
- package/plugin/channel/managed-connect/cli.ts +416 -0
- package/plugin/channel/managed-connect/device.ts +15 -0
- package/plugin/channel/managed-connect/local-cities.ts +94 -0
- package/plugin/channel/managed-connect/person-message.ts +74 -0
- package/plugin/channel/managed-connect/reception-bridge.ts +341 -0
- package/plugin/channel/managed-connect/relay-session.ts +7 -0
- package/plugin/channel/managed-connect/storage.ts +653 -0
- package/plugin/channel/managed-connect/transport.ts +87 -0
- package/plugin/channel/managed-connect-cli.js +4777 -0
- package/plugin/channel/managed-connect-cli.ts +7 -0
- package/plugin/channel/managed-connect-client.d.ts +261 -0
- package/plugin/channel/managed-connect-client.js +6572 -0
- package/plugin/channel/managed-connect-client.manifest.json +38 -0
- package/plugin/channel/package-lock.json +123 -105
- package/plugin/channel/package.json +4 -4
- package/plugin/channel/protocol.ts +4 -0
- package/plugin/channel/reception.ts +896 -0
- package/plugin/channel/road-cli.ts +1 -1
- package/plugin/channel/runtime/arnes.json +189 -0
- package/plugin/channel/runtime/arnes.ts +47 -0
- package/plugin/channel/runtime/claude.ts +18 -0
- package/plugin/channel/runtime/codex-config.ts +51 -1
- package/plugin/channel/runtime/codex.ts +31 -11
- package/plugin/channel/runtime/kimi.ts +6 -4
- package/plugin/channel/runtime-files.ts +39 -5
- package/plugin/channel/runtime-gateway.js +426 -100
- package/plugin/channel/runtime-gateway.ts +12 -0
- package/plugin/channel/trust/agents-city-sandbox-roots.json +66 -0
- package/plugin/scripts/arnes.py +271 -0
- package/plugin/scripts/busca.py +46 -10
- package/plugin/scripts/cage.py +5 -0
- package/plugin/scripts/city-session.sh +144 -28
- package/plugin/scripts/crecimiento.py +4 -1
- package/plugin/scripts/demos.py +58 -43
- package/plugin/scripts/desinstala.py +36 -21
- package/plugin/scripts/diario.py +119 -0
- package/plugin/scripts/doctor.py +90 -15
- package/plugin/scripts/read-card.py +48 -8
- package/plugin/scripts/reception.py +665 -0
|
@@ -250,6 +250,18 @@ function startConsole(): void {
|
|
|
250
250
|
output: process.stdout,
|
|
251
251
|
prompt: 'city> ',
|
|
252
252
|
});
|
|
253
|
+
// Say what this prompt is before anybody has to guess.
|
|
254
|
+
//
|
|
255
|
+
// Claude runs headless behind this gateway so that every turn crosses the
|
|
256
|
+
// bus and lands in the Hall — which is the whole point, and which also means
|
|
257
|
+
// the pane is NOT the Claude Code interface somebody was expecting. A bare
|
|
258
|
+
// `city>` after a wall of startup lines reads as "it failed to open"; it took
|
|
259
|
+
// exactly one person meeting it for the first time to prove that.
|
|
260
|
+
process.stdout.write(
|
|
261
|
+
`\n This window is ${options.actor}, talking to ${runtime} through the city.\n` +
|
|
262
|
+
' Type here to give it work. The full conversation appears in the town hall.\n' +
|
|
263
|
+
' /exit closes this window.\n\n',
|
|
264
|
+
);
|
|
253
265
|
terminal.prompt();
|
|
254
266
|
terminal.on('line', (line) => {
|
|
255
267
|
const prompt = line.trim();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol": "agents-city-key-transparency-root-chain/1",
|
|
3
|
+
"roots": [
|
|
4
|
+
{
|
|
5
|
+
"signed": {
|
|
6
|
+
"protocol": "agents-city-key-transparency-root/1",
|
|
7
|
+
"version": 1,
|
|
8
|
+
"environment": "sandbox",
|
|
9
|
+
"controlPlaneUrl": "https://agents-city-connect-sandbox.pages.dev",
|
|
10
|
+
"relayUrl": "wss://agents-city-relay-sandbox.bluebell.workers.dev/v1/connect",
|
|
11
|
+
"issuedAt": 1788063673370,
|
|
12
|
+
"expiresAt": 1819686073370,
|
|
13
|
+
"previousRootHash": null,
|
|
14
|
+
"keys": {
|
|
15
|
+
"agents-city-sandbox-operator-v1": {
|
|
16
|
+
"kty": "OKP",
|
|
17
|
+
"crv": "Ed25519",
|
|
18
|
+
"x": "ZIEWw4EUQ3qyoKVowZsl4nJuiVom7EwUMctI8NljFF8",
|
|
19
|
+
"ext": true
|
|
20
|
+
},
|
|
21
|
+
"agents-city-sandbox-root-v1": {
|
|
22
|
+
"kty": "OKP",
|
|
23
|
+
"crv": "Ed25519",
|
|
24
|
+
"x": "S8DifYMze6j2U-tRF_5tkjsXw5cmcFabt8B1X3ON1N8",
|
|
25
|
+
"ext": true
|
|
26
|
+
},
|
|
27
|
+
"agents-city-sandbox-witness-v1": {
|
|
28
|
+
"kty": "OKP",
|
|
29
|
+
"crv": "Ed25519",
|
|
30
|
+
"x": "fErzxNY6ZMfwH98E9F7jbn-tw_nOHVN3B3VodL62m68",
|
|
31
|
+
"ext": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"roles": {
|
|
35
|
+
"root": {
|
|
36
|
+
"keyIds": [
|
|
37
|
+
"agents-city-sandbox-root-v1"
|
|
38
|
+
],
|
|
39
|
+
"threshold": 1
|
|
40
|
+
},
|
|
41
|
+
"operator": {
|
|
42
|
+
"keyIds": [
|
|
43
|
+
"agents-city-sandbox-operator-v1"
|
|
44
|
+
],
|
|
45
|
+
"threshold": 1
|
|
46
|
+
},
|
|
47
|
+
"witness": {
|
|
48
|
+
"keyIds": [
|
|
49
|
+
"agents-city-sandbox-witness-v1"
|
|
50
|
+
],
|
|
51
|
+
"threshold": 1
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"maximumHeadAgeMs": 300000,
|
|
55
|
+
"maximumWitnessLagMs": 30000
|
|
56
|
+
},
|
|
57
|
+
"signatures": [
|
|
58
|
+
{
|
|
59
|
+
"protocol": "agents-city-key-transparency-root-signature/1",
|
|
60
|
+
"keyId": "agents-city-sandbox-root-v1",
|
|
61
|
+
"signature": "llGO2Kr236CfD5JCvaMjvtwW4gG98s-p4gowrKNl_fPKJ9ywPI9m6R_DH9uTbLgt6eGyXiTKxB4QVDWHMAloAg"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""What this product does to the CLIs you already had, on this machine.
|
|
3
|
+
|
|
4
|
+
The pitch is that Agents City orchestrates the CLIs people already run, with
|
|
5
|
+
the plugins, skills, MCP servers and settings they already configured. That is
|
|
6
|
+
a claim, and a claim about somebody else's machine is worth exactly as much as
|
|
7
|
+
the command that checks it. This is that command's engine.
|
|
8
|
+
|
|
9
|
+
It reads `plugin/channel/runtime/arnes.json` — the same declaration the
|
|
10
|
+
connectors take their policy values from, so the report cannot describe a
|
|
11
|
+
behaviour the code does not have — and fills it in with what is actually on
|
|
12
|
+
this disk: their real settings file, their real plugins, their real MCP
|
|
13
|
+
servers, their real model.
|
|
14
|
+
|
|
15
|
+
Three columns, and the distinction between them is the whole point:
|
|
16
|
+
|
|
17
|
+
el trato what we add or override. Short, deliberate, and the reason the
|
|
18
|
+
product works at all: without it the bus is not the only route
|
|
19
|
+
and the cage does not hold.
|
|
20
|
+
heredamos what we deliberately do NOT send, so their own CLI reads their
|
|
21
|
+
own configuration for it.
|
|
22
|
+
respetamos what loads untouched.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
AQUI = os.path.dirname(os.path.abspath(__file__))
|
|
29
|
+
RAIZ = os.path.dirname(os.path.dirname(AQUI))
|
|
30
|
+
DECLARACION = os.path.join(RAIZ, "plugin", "channel", "runtime", "arnes.json")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def declaracion():
|
|
34
|
+
try:
|
|
35
|
+
with open(DECLARACION, encoding="utf-8") as f:
|
|
36
|
+
return json.load(f).get("motores", {})
|
|
37
|
+
except (OSError, ValueError):
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _expande(ruta):
|
|
42
|
+
return os.path.expanduser(ruta)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _lee_json(ruta):
|
|
46
|
+
try:
|
|
47
|
+
with open(_expande(ruta), encoding="utf-8") as f:
|
|
48
|
+
return json.load(f)
|
|
49
|
+
except (OSError, ValueError):
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _lee_toml_plano(ruta):
|
|
54
|
+
"""Top-level `key = value` pairs, which is where the settings that matter
|
|
55
|
+
live. The same deliberately small reader the Codex connector uses, so the
|
|
56
|
+
two agree about what they can see."""
|
|
57
|
+
fuera = {}
|
|
58
|
+
try:
|
|
59
|
+
with open(_expande(ruta), encoding="utf-8") as f:
|
|
60
|
+
for linea in f:
|
|
61
|
+
linea = linea.strip()
|
|
62
|
+
if linea.startswith("["):
|
|
63
|
+
break
|
|
64
|
+
if not linea or linea.startswith("#") or "=" not in linea:
|
|
65
|
+
continue
|
|
66
|
+
clave, valor = linea.split("=", 1)
|
|
67
|
+
fuera[clave.strip()] = valor.strip().strip("\"'")
|
|
68
|
+
except OSError:
|
|
69
|
+
return {}
|
|
70
|
+
return fuera
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resume(valor):
|
|
74
|
+
"""One of their values, as a line somebody can read.
|
|
75
|
+
|
|
76
|
+
A permissions block is forty rules and a nested dict; printing it raw turns
|
|
77
|
+
a report into a wall and buries the three lines that matter. Structure is
|
|
78
|
+
counted, scalars are shown, and a long string is cut — but never silently:
|
|
79
|
+
what is cut says so.
|
|
80
|
+
"""
|
|
81
|
+
if isinstance(valor, dict):
|
|
82
|
+
cuantos = sum(len(v) if isinstance(v, list) else 1 for v in valor.values())
|
|
83
|
+
return f"{cuantos} entries"
|
|
84
|
+
if isinstance(valor, list):
|
|
85
|
+
return f"{len(valor)} entries"
|
|
86
|
+
texto = str(valor)
|
|
87
|
+
return texto if len(texto) <= 60 else texto[:57] + "…"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _suyo(config):
|
|
91
|
+
"""Their configuration, as far as we can honestly read it."""
|
|
92
|
+
for ruta in config:
|
|
93
|
+
if ruta.endswith(".toml"):
|
|
94
|
+
datos = _lee_toml_plano(ruta)
|
|
95
|
+
if datos:
|
|
96
|
+
return ruta, datos
|
|
97
|
+
else:
|
|
98
|
+
datos = _lee_json(ruta)
|
|
99
|
+
if datos:
|
|
100
|
+
return ruta, datos
|
|
101
|
+
return (config[0] if config else ""), {}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _cuenta_claude(datos):
|
|
105
|
+
"""What their Claude actually loads, counted rather than assumed."""
|
|
106
|
+
fuera = {}
|
|
107
|
+
plugins = datos.get("enabledPlugins")
|
|
108
|
+
if isinstance(plugins, dict):
|
|
109
|
+
fuera["plugins"] = sum(1 for v in plugins.values() if v)
|
|
110
|
+
permisos = datos.get("permissions")
|
|
111
|
+
if isinstance(permisos, dict):
|
|
112
|
+
reglas = sum(len(v) for v in permisos.values() if isinstance(v, list))
|
|
113
|
+
if reglas:
|
|
114
|
+
fuera["permission rules"] = reglas
|
|
115
|
+
mcp = _lee_json("~/.claude.json")
|
|
116
|
+
if isinstance(mcp, dict) and isinstance(mcp.get("mcpServers"), dict):
|
|
117
|
+
fuera["MCP servers"] = len(mcp["mcpServers"])
|
|
118
|
+
return fuera
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def informe(instalado=None):
|
|
122
|
+
"""The whole table, one entry per runtime, ready to print or to serve.
|
|
123
|
+
|
|
124
|
+
`instalado` is injected so the report can be built on a machine that has
|
|
125
|
+
none of them — a test, or a person reading the docs — without pretending
|
|
126
|
+
they are there.
|
|
127
|
+
"""
|
|
128
|
+
if instalado is None:
|
|
129
|
+
import shutil
|
|
130
|
+
|
|
131
|
+
def instalado(binario):
|
|
132
|
+
return bool(shutil.which(binario))
|
|
133
|
+
|
|
134
|
+
fuera = []
|
|
135
|
+
for nombre, motor in sorted(declaracion().items()):
|
|
136
|
+
ruta, datos = _suyo(motor.get("config", []))
|
|
137
|
+
# A flat reader over a TOML file sees the top level and stops.
|
|
138
|
+
parcial = ruta.endswith(".toml") and bool(datos)
|
|
139
|
+
hereda = []
|
|
140
|
+
for h in motor.get("hereda", []):
|
|
141
|
+
valor = datos.get(h["suyo"])
|
|
142
|
+
hereda.append(
|
|
143
|
+
{
|
|
144
|
+
"clave": h["suyo"],
|
|
145
|
+
"cuando": h.get("cuando", ""),
|
|
146
|
+
"valor": "" if valor is None else resume(valor),
|
|
147
|
+
# Found, or merely not found HERE? This reader sees the top
|
|
148
|
+
# level of a TOML file and stops, so a key it did not find
|
|
149
|
+
# may be absent or may be inside a `[section]`. Saying "not
|
|
150
|
+
# set" would be the report inventing a fact about somebody
|
|
151
|
+
# else's machine.
|
|
152
|
+
"leido": h["suyo"] in datos,
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
avisos = []
|
|
156
|
+
for aviso in motor.get("avisa", []):
|
|
157
|
+
clave, _, esperado = aviso["cuando"].partition(" = ")
|
|
158
|
+
if esperado and str(datos.get(clave.strip(), "")) == esperado.strip():
|
|
159
|
+
avisos.append(aviso["dice"])
|
|
160
|
+
entrada = {
|
|
161
|
+
"motor": nombre,
|
|
162
|
+
"binario": motor.get("binario", nombre),
|
|
163
|
+
"instalado": instalado(motor.get("binario", nombre)),
|
|
164
|
+
"config": ruta,
|
|
165
|
+
"hay_config": bool(datos),
|
|
166
|
+
"trato": motor.get("trato", []),
|
|
167
|
+
"hereda": hereda,
|
|
168
|
+
"respeta": motor.get("respeta", []),
|
|
169
|
+
"avisos": avisos,
|
|
170
|
+
# A property of the FILE, not of each key read out of it.
|
|
171
|
+
"parcial": parcial,
|
|
172
|
+
}
|
|
173
|
+
if nombre == "claude" and datos:
|
|
174
|
+
entrada["cuenta"] = _cuenta_claude(datos)
|
|
175
|
+
fuera.append(entrada)
|
|
176
|
+
return fuera
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def banderas(nombre):
|
|
180
|
+
"""The declared deal for one runtime, as command-line flags.
|
|
181
|
+
|
|
182
|
+
Claude's half of the deal used to live as two shell literals in
|
|
183
|
+
`city-session.sh` while `arnes.json` declared the same three values — so
|
|
184
|
+
the drift guard, which only scans the connectors, passed **vacuously** for
|
|
185
|
+
the one runtime whose values were actually respelled somewhere else. The
|
|
186
|
+
launcher asks for them now, which makes the declaration true for all four
|
|
187
|
+
instead of two.
|
|
188
|
+
|
|
189
|
+
settings a key in the `--settings` layer Claude merges on top of theirs
|
|
190
|
+
flag a flag of its own, `--kebab-case-of-the-key value`
|
|
191
|
+
"""
|
|
192
|
+
motor = declaracion().get(nombre, {})
|
|
193
|
+
ajustes, flags = {}, []
|
|
194
|
+
for t in motor.get("trato", []):
|
|
195
|
+
valor = t["valor"]
|
|
196
|
+
if t.get("rinde") == "settings":
|
|
197
|
+
ajustes[t["clave"]] = {"true": True, "false": False}.get(valor, valor)
|
|
198
|
+
elif t.get("rinde") == "flag":
|
|
199
|
+
guion = "".join("-" + c.lower() if c.isupper() else c for c in t["clave"])
|
|
200
|
+
flags.append(f"--{guion} {valor}")
|
|
201
|
+
partes = []
|
|
202
|
+
if ajustes:
|
|
203
|
+
partes.append("--settings " + json.dumps(ajustes, separators=(",", ":")))
|
|
204
|
+
partes += flags
|
|
205
|
+
return " ".join(partes)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
#: Enough for the widest label in the report ("we inherit", "untouched").
|
|
209
|
+
ANCHO = 13
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _linea(etiqueta, texto):
|
|
213
|
+
return f" {etiqueta.ljust(ANCHO)}{texto}"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def imprime(entradas, di=print):
|
|
217
|
+
"""The report, as somebody deciding whether to trust this would read it."""
|
|
218
|
+
di("\n What Agents City does to the CLIs you already have\n")
|
|
219
|
+
for e in entradas:
|
|
220
|
+
estado = "" if e["instalado"] else " (not installed here)"
|
|
221
|
+
di(f" {e['motor']}{estado}")
|
|
222
|
+
if not e["trato"]:
|
|
223
|
+
di(_linea("the deal", "nothing — it runs exactly as you configured it"))
|
|
224
|
+
for t in e["trato"]:
|
|
225
|
+
suyo = f" · yours wins ({t['suyo']})" if t.get("suyo") else ""
|
|
226
|
+
di(_linea("the deal", f"{t['clave']} = {resume(t['valor'])}{suyo}"))
|
|
227
|
+
di(_linea("", f" via {t['via']}"))
|
|
228
|
+
di(_linea("", f" because {t['porque']}"))
|
|
229
|
+
for h in e["hereda"]:
|
|
230
|
+
if h["valor"]:
|
|
231
|
+
valor = f" = {h['valor']}"
|
|
232
|
+
elif not e["hay_config"]:
|
|
233
|
+
valor = ""
|
|
234
|
+
elif not e["parcial"]:
|
|
235
|
+
valor = " (not set)"
|
|
236
|
+
else:
|
|
237
|
+
valor = " (not at the top level; this reader stops at [sections])"
|
|
238
|
+
di(_linea("we inherit", f"{h['clave']}{valor} · {h['cuando']}"))
|
|
239
|
+
if e["respeta"]:
|
|
240
|
+
di(_linea("untouched", ", ".join(e["respeta"])))
|
|
241
|
+
for clave, cuantos in (e.get("cuenta") or {}).items():
|
|
242
|
+
di(_linea("", f"{cuantos} {clave} on this machine"))
|
|
243
|
+
if e["hay_config"]:
|
|
244
|
+
di(_linea("read from", e["config"]))
|
|
245
|
+
for aviso in e["avisos"]:
|
|
246
|
+
di(_linea("careful", aviso))
|
|
247
|
+
di("")
|
|
248
|
+
di(" The deal is what makes the bus the only route and the cage hold.")
|
|
249
|
+
di(" CITY_CAGE=0 removes the cage. Everything else above is yours.\n")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def main(argv=None):
|
|
253
|
+
import sys
|
|
254
|
+
|
|
255
|
+
argv = sys.argv[1:] if argv is None else argv
|
|
256
|
+
# `arnes.py flags <runtime>` is what the launcher asks, so the deal it
|
|
257
|
+
# applies and the deal this reports are one answer.
|
|
258
|
+
if argv[:1] == ["flags"]:
|
|
259
|
+
print(banderas(argv[1] if len(argv) > 1 else ""))
|
|
260
|
+
return 0
|
|
261
|
+
entradas = informe()
|
|
262
|
+
if "--json" in argv:
|
|
263
|
+
json.dump(entradas, sys.stdout, ensure_ascii=False, indent=1)
|
|
264
|
+
print()
|
|
265
|
+
else:
|
|
266
|
+
imprime(entradas)
|
|
267
|
+
return 0
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if __name__ == "__main__":
|
|
271
|
+
raise SystemExit(main())
|
package/plugin/scripts/busca.py
CHANGED
|
@@ -86,7 +86,23 @@ SALTAR = {
|
|
|
86
86
|
|
|
87
87
|
#: What counts as a document. Deliberately short: the question is "is there
|
|
88
88
|
#: writing in here", not "can this folder be indexed".
|
|
89
|
-
|
|
89
|
+
#:
|
|
90
|
+
#: One answer, shared with `crecimiento`, which grows a knowledge agent's house
|
|
91
|
+
#: by counting the same files. Two lists here meant a visible contradiction: a
|
|
92
|
+
#: folder of `.adoc` was never offered as a knowledge house and yet grew one
|
|
93
|
+
#: once mounted by hand.
|
|
94
|
+
DOCUMENTOS = (
|
|
95
|
+
".md",
|
|
96
|
+
".markdown",
|
|
97
|
+
".txt",
|
|
98
|
+
".rst",
|
|
99
|
+
".org",
|
|
100
|
+
".adoc",
|
|
101
|
+
".pdf",
|
|
102
|
+
".doc",
|
|
103
|
+
".docx",
|
|
104
|
+
".rtf",
|
|
105
|
+
)
|
|
90
106
|
|
|
91
107
|
#: A folder needs this many documents before it is worth offering. Two loose
|
|
92
108
|
#: readmes are not a knowledge base.
|
|
@@ -350,17 +366,22 @@ def _lee():
|
|
|
350
366
|
return sitios
|
|
351
367
|
|
|
352
368
|
|
|
353
|
-
|
|
369
|
+
#: How long the index is trusted. A day: long enough that nobody pays for the
|
|
370
|
+
#: crawl twice in a sitting, short enough that yesterday's clones show up.
|
|
371
|
+
VIDA = 86400
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def caduco():
|
|
354
375
|
try:
|
|
355
|
-
return time.time() - os.path.getmtime(fichero_cache()) >
|
|
376
|
+
return time.time() - os.path.getmtime(fichero_cache()) > VIDA
|
|
356
377
|
except OSError:
|
|
357
378
|
return True
|
|
358
379
|
|
|
359
380
|
|
|
360
|
-
def lugares(refrescar=False
|
|
381
|
+
def lugares(refrescar=False):
|
|
361
382
|
"""Every place, from the cache while it is fresh. The first crawl of a full
|
|
362
383
|
home directory is not fast and nobody should pay for it twice."""
|
|
363
|
-
if not refrescar and not caduco(
|
|
384
|
+
if not refrescar and not caduco():
|
|
364
385
|
guardado = _lee()
|
|
365
386
|
if guardado is not None:
|
|
366
387
|
return guardado
|
|
@@ -379,11 +400,26 @@ def repos(refrescar=False):
|
|
|
379
400
|
|
|
380
401
|
def ruta_de(nombre):
|
|
381
402
|
"""One place's path by name, rebuilding the index once if it is not there —
|
|
382
|
-
the repo somebody cloned a minute ago is exactly the one they came to use.
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
403
|
+
the repo somebody cloned a minute ago is exactly the one they came to use.
|
|
404
|
+
|
|
405
|
+
Once, though. The launcher resolves every name on a card, each in its own
|
|
406
|
+
process, so a card with three stale names used to mean three full crawls of
|
|
407
|
+
the home directory in a row: the rebuild triggered by the first name is not
|
|
408
|
+
going to make the second one appear. An index rebuilt seconds ago is
|
|
409
|
+
treated as the answer.
|
|
410
|
+
"""
|
|
411
|
+
for s in lugares():
|
|
412
|
+
if s["nombre"] == nombre:
|
|
413
|
+
return s["ruta"]
|
|
414
|
+
try:
|
|
415
|
+
recien = time.time() - os.path.getmtime(fichero_cache()) < 30
|
|
416
|
+
except OSError:
|
|
417
|
+
recien = False
|
|
418
|
+
if recien:
|
|
419
|
+
return ""
|
|
420
|
+
for s in lugares(refrescar=True):
|
|
421
|
+
if s["nombre"] == nombre:
|
|
422
|
+
return s["ruta"]
|
|
387
423
|
return ""
|
|
388
424
|
|
|
389
425
|
|
package/plugin/scripts/cage.py
CHANGED
|
@@ -136,6 +136,11 @@ def sellados_por_tipo(casa):
|
|
|
136
136
|
for f in SECRETOS_FICHERO:
|
|
137
137
|
fuera[rutas.canonicaliza(os.path.join(casa, f))] = 'file'
|
|
138
138
|
fuera[rutas.canonicaliza(os.path.join(ac, '.runtime', 'broker'))] = 'dir'
|
|
139
|
+
# Managed Roads use a device-held Ed25519 signing key and X25519 decryption
|
|
140
|
+
# key. Repo-agent windows never need either: the local hub is started by the
|
|
141
|
+
# human-facing connect command and owns the outbound session. Seal the whole
|
|
142
|
+
# state directory on both kernels, including before it exists.
|
|
143
|
+
fuera[rutas.canonicaliza(os.path.join(ac, '.runtime', 'connect'))] = 'dir'
|
|
139
144
|
for f in ('credentials', 'credentials.toml'):
|
|
140
145
|
fuera[rutas.canonicaliza(os.path.join(casa, '.cargo', f))] = 'file'
|
|
141
146
|
# An owner's own denies: nothing declares their kind, so ask the disk and
|