theus-cli-linux-arm64 1.2.0 → 1.2.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.
@@ -0,0 +1,36 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Theus en Chrome",
4
+ "version": "0.3.0",
5
+ "description": "Conecta tu navegador con Theus: el CLI ve tus pestañas, lee páginas, hace capturas y ejecuta acciones que tú autorizas.",
6
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAocpSkZEGHsT5kMKBGvvbJaKq7E+JzkAOdTAmmLyWjpKkzTlu7rkjnwhByTr2wjfToqvH7cfyRYUg98HoxO80Pqck3lWhMhqWro8VfGvIQyxoGJenF9nG4At9HWZIdPlVZp6npgLgUUuGXRpWLbqJvbmfMGjIQTrp9gStkatCFQWzK04PrGadbxHM05K+YNedA7/+4O95bsGccHgo7lCpQ71s47KKYrDC7EKRaAIIvkq34mJiy4T+LxMP4iSLqTHBC+nt0sDvT+UZ39Q51LNAW1VNYqG5/E2NPzzCjnn06Qzsotbo6zu6YW6VQbYQIqgu/OLeV/1aGqhrL0mxdHqLlwIDAQAB",
7
+ "permissions": [
8
+ "nativeMessaging",
9
+ "tabs",
10
+ "scripting",
11
+ "activeTab",
12
+ "debugger"
13
+ ],
14
+ "host_permissions": [
15
+ "<all_urls>"
16
+ ],
17
+ "background": {
18
+ "service_worker": "background.js"
19
+ },
20
+ "action": {
21
+ "default_popup": "popup.html",
22
+ "default_title": "Theus en Chrome",
23
+ "default_icon": {
24
+ "16": "icons/icon16.png",
25
+ "32": "icons/icon32.png",
26
+ "48": "icons/icon48.png",
27
+ "128": "icons/icon128.png"
28
+ }
29
+ },
30
+ "icons": {
31
+ "16": "icons/icon16.png",
32
+ "32": "icons/icon32.png",
33
+ "48": "icons/icon48.png",
34
+ "128": "icons/icon128.png"
35
+ }
36
+ }
@@ -0,0 +1,78 @@
1
+ <!doctype html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Theus en Chrome</title>
6
+ <style>
7
+ body {
8
+ font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
9
+ background: #0d0d10;
10
+ color: #f2f2f2;
11
+ margin: 0;
12
+ min-width: 260px;
13
+ padding: 16px;
14
+ }
15
+ h1 {
16
+ font-size: 14px;
17
+ margin: 0 0 12px;
18
+ letter-spacing: 0.04em;
19
+ }
20
+ .fila {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 8px;
24
+ margin: 8px 0;
25
+ font-size: 13px;
26
+ }
27
+ .punto {
28
+ width: 9px;
29
+ height: 9px;
30
+ border-radius: 50%;
31
+ background: #666;
32
+ flex: none;
33
+ }
34
+ .punto.ok { background: #3ecf6a; }
35
+ .punto.mal { background: #d9534f; }
36
+ .nota {
37
+ margin-top: 12px;
38
+ font-size: 11px;
39
+ color: #9a9aa2;
40
+ line-height: 1.5;
41
+ }
42
+ #error {
43
+ margin-top: 8px;
44
+ font-size: 11px;
45
+ color: #e0a1a1;
46
+ word-break: break-word;
47
+ display: none;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <h1>THEUS EN CHROME</h1>
53
+ <div class="fila">
54
+ <span id="punto-host" class="punto"></span>
55
+ <span id="texto-host">Comprobando host nativo…</span>
56
+ </div>
57
+ <div class="fila">
58
+ <span id="punto-cli" class="punto"></span>
59
+ <span id="texto-cli">Comprobando CLI…</span>
60
+ </div>
61
+ <div id="error"></div>
62
+ <p class="nota">
63
+ El host nativo lo sirve el CLI de Theus. Si aparece desconectado,
64
+ ejecuta <strong>theus --chrome</strong> en tu terminal y reabre este
65
+ panel. El comando <strong>/chrome</strong> dentro del CLI muestra el
66
+ estado completo de la integración.
67
+ </p>
68
+ <p class="nota">
69
+ Theus puede controlar tus pestañas: clics, tecleo, capturas de pantalla
70
+ y lectura de consola y de red. Mientras una pestaña esté bajo control,
71
+ Chrome muestra la barra «Theus en Chrome ha empezado a depurar este
72
+ navegador»: es un aviso del propio Chrome, inevitable, y desaparece al
73
+ terminar. Si pulsas «Cancelar» en esa barra, el control se corta (Theus
74
+ lo retoma en la siguiente acción).
75
+ </p>
76
+ <script src="popup.js"></script>
77
+ </body>
78
+ </html>
@@ -0,0 +1,47 @@
1
+ // Popup de estado de «Theus en Chrome».
2
+ function pintar(estado) {
3
+ const puntoHost = document.getElementById('punto-host')
4
+ const textoHost = document.getElementById('texto-host')
5
+ const puntoCli = document.getElementById('punto-cli')
6
+ const textoCli = document.getElementById('texto-cli')
7
+ const errorEl = document.getElementById('error')
8
+
9
+ if (estado && estado.hostConnected) {
10
+ puntoHost.className = 'punto ok'
11
+ textoHost.textContent = 'Host nativo: conectado'
12
+ } else {
13
+ puntoHost.className = 'punto mal'
14
+ textoHost.textContent = 'Host nativo: desconectado'
15
+ }
16
+
17
+ if (estado && estado.cliConnected) {
18
+ puntoCli.className = 'punto ok'
19
+ textoCli.textContent =
20
+ estado.cliClients > 1
21
+ ? `CLI de Theus: conectado (${estado.cliClients} sesiones)`
22
+ : 'CLI de Theus: conectado'
23
+ } else {
24
+ puntoCli.className = 'punto mal'
25
+ textoCli.textContent = 'CLI de Theus: sin sesión conectada'
26
+ }
27
+
28
+ if (estado && estado.lastError) {
29
+ errorEl.style.display = 'block'
30
+ errorEl.textContent = `Último error: ${estado.lastError}`
31
+ } else {
32
+ errorEl.style.display = 'none'
33
+ }
34
+ }
35
+
36
+ function refrescar() {
37
+ chrome.runtime.sendMessage({ type: 'get_state' }, estado => {
38
+ if (chrome.runtime.lastError) {
39
+ pintar(null)
40
+ return
41
+ }
42
+ pintar(estado)
43
+ })
44
+ }
45
+
46
+ refrescar()
47
+ setInterval(refrescar, 2000)
@@ -0,0 +1,316 @@
1
+ // Theus en Chrome — grabador de Kuti (fase 1), CONTENT SCRIPT.
2
+ //
3
+ // chrome.debugger NO reporta el input del usuario (el dominio Input de CDP es
4
+ // solo INYECCIÓN). Para GRABAR lo que la persona hace de verdad hace falta un
5
+ // content script que escuche los eventos del DOM. Este archivo lo inyecta
6
+ // background.js con chrome.scripting.executeScript cuando arranca /grabar auto,
7
+ // y lo reinyecta tras cada navegación (el content script muere al cambiar de
8
+ // página).
9
+ //
10
+ // Por cada evento calcula un DESCRIPTOR del elemento por INTENCIÓN (rol ARIA,
11
+ // nombre accesible, texto visible, selector css corto, name/id/placeholder/
12
+ // tipo/autocomplete) y lo manda a background.js por chrome.runtime.sendMessage.
13
+ //
14
+ // PRIVACIDAD: nunca se envía el texto tecleado carácter a carácter. El valor
15
+ // final de un campo se toma del evento `change` (o del `focusout`). Los campos
16
+ // sensibles (password, autocomplete cc-*, name/id/placeholder con clave/token/
17
+ // cvv…) se registran SIN su valor: background/CLI los volverán {{secreto}}.
18
+
19
+ ;(function () {
20
+ // Idempotencia: si ya estamos grabando en este documento, no dupliques
21
+ // escuchas (una reinyección accidental no debe multiplicar eventos).
22
+ if (window.__kutiGrabadorActivo) return
23
+ window.__kutiGrabadorActivo = true
24
+
25
+ const RE_NOMBRE_SECRETO =
26
+ /(contrase|clave|password|passwd|\bpwd\b|secret|token|api[\s_-]?key|\bcvv\b|\bcvc\b|\bcsc\b|\botp\b|\bpin\b|codigo\s*de\s*seguridad|security\s*code)/i
27
+
28
+ // Recuerda el último valor enviado por elemento, para no duplicar `escribir`
29
+ // cuando change y focusout disparan por el mismo cambio.
30
+ const ultimoEnviado = new WeakMap()
31
+ // Marca de «sucio»: un input cambió y aún no se ha confirmado con change.
32
+ const sucios = new WeakSet()
33
+
34
+ function enviar(evento) {
35
+ try {
36
+ chrome.runtime.sendMessage({ type: 'kuti_recorder_event', evento })
37
+ } catch (_e) {
38
+ // El contexto de la extensión puede haberse invalidado (recarga): ignora.
39
+ }
40
+ }
41
+
42
+ // ---- Descriptor del elemento ------------------------------------------
43
+
44
+ function rolImplicito(el) {
45
+ const tag = el.tagName ? el.tagName.toLowerCase() : ''
46
+ if (tag === 'a' && el.hasAttribute('href')) return 'link'
47
+ if (tag === 'button') return 'button'
48
+ if (tag === 'select') return 'combobox'
49
+ if (tag === 'textarea') return 'textbox'
50
+ if (tag === 'summary') return 'button'
51
+ if (tag === 'input') {
52
+ const t = (el.getAttribute('type') || 'text').toLowerCase()
53
+ if (t === 'checkbox') return 'checkbox'
54
+ if (t === 'radio') return 'radio'
55
+ if (t === 'button' || t === 'submit' || t === 'reset') return 'button'
56
+ if (t === 'range') return 'slider'
57
+ return 'textbox'
58
+ }
59
+ return undefined
60
+ }
61
+
62
+ function textoDe(el) {
63
+ const t = (el.innerText || el.textContent || '').replace(/\s+/g, ' ').trim()
64
+ return t ? t.slice(0, 200) : ''
65
+ }
66
+
67
+ function nombreAccesible(el) {
68
+ const aria = el.getAttribute && el.getAttribute('aria-label')
69
+ if (aria && aria.trim()) return aria.trim()
70
+ const labelledby = el.getAttribute && el.getAttribute('aria-labelledby')
71
+ if (labelledby) {
72
+ const partes = labelledby
73
+ .split(/\s+/)
74
+ .map(id => {
75
+ const ref = document.getElementById(id)
76
+ return ref ? textoDe(ref) : ''
77
+ })
78
+ .filter(Boolean)
79
+ if (partes.length) return partes.join(' ').slice(0, 200)
80
+ }
81
+ // <label for="id"> o <label> envolvente.
82
+ if (el.id) {
83
+ const lab = document.querySelector(`label[for="${cssEscape(el.id)}"]`)
84
+ if (lab) {
85
+ const t = textoDe(lab)
86
+ if (t) return t
87
+ }
88
+ }
89
+ if (el.closest) {
90
+ const lab = el.closest('label')
91
+ if (lab) {
92
+ const t = textoDe(lab)
93
+ if (t) return t
94
+ }
95
+ }
96
+ const tag = el.tagName ? el.tagName.toLowerCase() : ''
97
+ // Botones/enlaces: su texto ES su nombre.
98
+ if (tag === 'button' || tag === 'a' || tag === 'summary') {
99
+ const t = textoDe(el)
100
+ if (t) return t
101
+ }
102
+ if (tag === 'input') {
103
+ const type = (el.getAttribute('type') || 'text').toLowerCase()
104
+ if ((type === 'submit' || type === 'button') && el.value) return el.value
105
+ }
106
+ const title = el.getAttribute && el.getAttribute('title')
107
+ if (title && title.trim()) return title.trim()
108
+ const ph = el.getAttribute && el.getAttribute('placeholder')
109
+ if (ph && ph.trim()) return ph.trim()
110
+ const name = el.getAttribute && el.getAttribute('name')
111
+ if (name && name.trim()) return name.trim()
112
+ return ''
113
+ }
114
+
115
+ function cssEscape(s) {
116
+ if (window.CSS && typeof window.CSS.escape === 'function') return CSS.escape(s)
117
+ return String(s).replace(/["\\#.:>\s]/g, '\\$&')
118
+ }
119
+
120
+ function selectorCorto(el) {
121
+ if (el.id) return `#${cssEscape(el.id)}`
122
+ const partes = []
123
+ let nodo = el
124
+ while (nodo && nodo.nodeType === 1 && partes.length < 5) {
125
+ let parte = nodo.tagName.toLowerCase()
126
+ if (nodo.id) {
127
+ partes.unshift(`#${cssEscape(nodo.id)}`)
128
+ break
129
+ }
130
+ const padre = nodo.parentElement
131
+ if (padre) {
132
+ const hermanos = Array.from(padre.children).filter(
133
+ c => c.tagName === nodo.tagName,
134
+ )
135
+ if (hermanos.length > 1) {
136
+ parte += `:nth-of-type(${hermanos.indexOf(nodo) + 1})`
137
+ }
138
+ }
139
+ partes.unshift(parte)
140
+ nodo = nodo.parentElement
141
+ }
142
+ return partes.join(' > ')
143
+ }
144
+
145
+ function tipoDe(el) {
146
+ const tag = el.tagName ? el.tagName.toLowerCase() : ''
147
+ if (tag === 'input') return (el.getAttribute('type') || 'text').toLowerCase()
148
+ if (tag === 'select') return 'select'
149
+ if (tag === 'textarea') return 'textarea'
150
+ return tag
151
+ }
152
+
153
+ function descriptor(el) {
154
+ const d = {}
155
+ const rol = (el.getAttribute && el.getAttribute('role')) || rolImplicito(el)
156
+ if (rol) d.rol = rol
157
+ const nombre = nombreAccesible(el)
158
+ if (nombre) d.nombre = nombre
159
+ const texto = textoDe(el)
160
+ if (texto) d.texto = texto
161
+ const css = selectorCorto(el)
162
+ if (css) d.css = css
163
+ const name = el.getAttribute && el.getAttribute('name')
164
+ if (name) d.name = name
165
+ if (el.id) d.id = el.id
166
+ const ph = el.getAttribute && el.getAttribute('placeholder')
167
+ if (ph) d.placeholder = ph
168
+ d.tipo = tipoDe(el)
169
+ const auto = el.getAttribute && el.getAttribute('autocomplete')
170
+ if (auto) d.autocomplete = auto
171
+ return d
172
+ }
173
+
174
+ function esSecreto(el, d) {
175
+ const tipo = (d.tipo || '').toLowerCase()
176
+ if (tipo === 'password') return true
177
+ const auto = (d.autocomplete || '').toLowerCase().trim()
178
+ if (auto.startsWith('cc-') || auto === 'one-time-code') return true
179
+ const heno = [d.name, d.id, d.placeholder, d.nombre]
180
+ .filter(Boolean)
181
+ .join(' ')
182
+ .normalize('NFD')
183
+ .replace(/[\u0300-\u036f]/g, '')
184
+ return RE_NOMBRE_SECRETO.test(heno)
185
+ }
186
+
187
+ function valorDe(el, d) {
188
+ const tag = el.tagName ? el.tagName.toLowerCase() : ''
189
+ if (tag === 'select') {
190
+ const opt = el.options && el.options[el.selectedIndex]
191
+ return opt ? (opt.textContent || opt.value || '').trim() : el.value || ''
192
+ }
193
+ if (tag === 'input') {
194
+ const t = (d.tipo || 'text')
195
+ if (t === 'checkbox' || t === 'radio') return el.checked ? 'true' : 'false'
196
+ return el.value != null ? String(el.value) : ''
197
+ }
198
+ if (tag === 'textarea') return el.value != null ? String(el.value) : ''
199
+ if (el.isContentEditable) return textoDe(el)
200
+ return ''
201
+ }
202
+
203
+ function esCampo(el) {
204
+ const tag = el.tagName ? el.tagName.toLowerCase() : ''
205
+ return (
206
+ tag === 'input' ||
207
+ tag === 'select' ||
208
+ tag === 'textarea' ||
209
+ (el.isContentEditable === true)
210
+ )
211
+ }
212
+
213
+ // ---- Envío de un cambio de campo (change/focusout) --------------------
214
+
215
+ function emitirCambio(el) {
216
+ if (!el || !esCampo(el)) return
217
+ const d = descriptor(el)
218
+ const secreto = esSecreto(el, d)
219
+ const valor = secreto ? undefined : valorDe(el, d)
220
+ // Dedupe: mismo valor ya enviado → no repitas.
221
+ const firma = secreto ? '\u0000secreto' : String(valor)
222
+ if (ultimoEnviado.get(el) === firma) return
223
+ ultimoEnviado.set(el, firma)
224
+ sucios.delete(el)
225
+ const evento = { t: 'change', objetivo: d }
226
+ if (secreto) evento.secreto = true
227
+ else evento.valor = valor
228
+ enviar(evento)
229
+ }
230
+
231
+ // ---- Escuchas ---------------------------------------------------------
232
+
233
+ function onClick(e) {
234
+ const el = e.target && e.target.closest ? e.target.closest('*') : e.target
235
+ if (!el || el.nodeType !== 1) return
236
+ // Los selects/checks/radios se registran por su change, no por el clic.
237
+ const tag = el.tagName ? el.tagName.toLowerCase() : ''
238
+ const tipo = tipoDe(el)
239
+ if (tag === 'select' || tipo === 'checkbox' || tipo === 'radio') return
240
+ // Interesan controles y elementos accionables; ignora clics en el vacío.
241
+ const accionable =
242
+ el.closest('a, button, [role="button"], [onclick], summary, input, label') ||
243
+ el
244
+ enviar({ t: 'click', objetivo: descriptor(accionable) })
245
+ }
246
+
247
+ function onInput(e) {
248
+ const el = e.target
249
+ if (el && esCampo(el)) sucios.add(el)
250
+ }
251
+
252
+ function onChange(e) {
253
+ emitirCambio(e.target)
254
+ }
255
+
256
+ function onFocusOut(e) {
257
+ // Red de seguridad: si el input cambió pero no disparó change (SPAs), el
258
+ // valor final igual se captura al perder el foco.
259
+ const el = e.target
260
+ if (el && sucios.has(el)) emitirCambio(el)
261
+ }
262
+
263
+ const TECLAS_SUELTAS = new Set(['Enter', 'Tab', 'Escape'])
264
+
265
+ function onKeyDown(e) {
266
+ // Solo teclas «de control» y atajos; NUNCA el texto tecleado.
267
+ const combo = e.ctrlKey || e.metaKey || e.altKey
268
+ if (!combo && !TECLAS_SUELTAS.has(e.key)) return
269
+ // Antes de una tecla de acción, asegura el valor del campo con foco.
270
+ const activo = document.activeElement
271
+ if (activo && sucios.has(activo)) emitirCambio(activo)
272
+ let tecla = ''
273
+ if (e.ctrlKey) tecla += 'ctrl+'
274
+ if (e.metaKey) tecla += 'meta+'
275
+ if (e.altKey) tecla += 'alt+'
276
+ if (e.shiftKey && (combo || e.key.length > 1)) tecla += 'shift+'
277
+ tecla += e.key
278
+ enviar({ t: 'key', tecla })
279
+ }
280
+
281
+ function onSubmit(e) {
282
+ const el = e.target
283
+ enviar({ t: 'submit', objetivo: el ? descriptor(el) : undefined })
284
+ }
285
+
286
+ const opciones = { capture: true, passive: true }
287
+ document.addEventListener('click', onClick, opciones)
288
+ document.addEventListener('input', onInput, opciones)
289
+ document.addEventListener('change', onChange, opciones)
290
+ document.addEventListener('focusout', onFocusOut, opciones)
291
+ document.addEventListener('keydown', onKeyDown, true)
292
+ document.addEventListener('submit', onSubmit, opciones)
293
+
294
+ function desmontar() {
295
+ document.removeEventListener('click', onClick, opciones)
296
+ document.removeEventListener('input', onInput, opciones)
297
+ document.removeEventListener('change', onChange, opciones)
298
+ document.removeEventListener('focusout', onFocusOut, opciones)
299
+ document.removeEventListener('keydown', onKeyDown, true)
300
+ document.removeEventListener('submit', onSubmit, opciones)
301
+ window.__kutiGrabadorActivo = false
302
+ }
303
+
304
+ // background.js manda parar cuando el usuario cierra la grabación.
305
+ chrome.runtime.onMessage.addListener(function alParar(msg) {
306
+ if (msg && msg.type === 'kuti_recorder_stop') {
307
+ desmontar()
308
+ try {
309
+ chrome.runtime.onMessage.removeListener(alParar)
310
+ } catch (_e) {
311
+ // ignore
312
+ }
313
+ }
314
+ return false
315
+ })
316
+ })()
@@ -7,7 +7,7 @@
7
7
  "url": "https://theus.pe"
8
8
  },
9
9
  "homepage": "https://theus.pe",
10
- "repository": "https://github.com/MarketrixPE/theus-arsenal",
10
+ "repository": "https://github.com/astratechnology/theus-arsenal",
11
11
  "license": "MIT",
12
12
  "keywords": [
13
13
  "agentes",
@@ -20,7 +20,7 @@ que elevan la experiencia del desarrollador.
20
20
  Dentro del CLI de Theus:
21
21
 
22
22
  ```
23
- /plugin marketplace add MarketrixPE/theus-arsenal
23
+ /plugin marketplace add astratechnology/theus-arsenal
24
24
  /plugin install arsenal@theus
25
25
  ```
26
26