command-cmd 1.0.2 → 1.0.4

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 (6) hide show
  1. package/cmd.js +1640 -16
  2. package/doc.md +199 -157
  3. package/docPTBR.md +204 -157
  4. package/map.md +238 -0
  5. package/mapPTBR.md +238 -0
  6. package/package.json +5 -2
package/docPTBR.md CHANGED
@@ -1,19 +1,12 @@
1
1
  # command-cmd (Documentacao PT-BR)
2
2
 
3
- `command-cmd` e uma biblioteca para detectar e extrair comandos estruturados de texto livre, com foco em respostas de IA.
3
+ `command-cmd` extrai comandos de texto de IA e executa automacoes com cursor via `robotjs`.
4
4
 
5
- Ela e util quando o modelo retorna linguagem natural junto com blocos de comando e voce precisa transformar isso em dados para automacao.
5
+ Funcoes exportadas:
6
6
 
7
- ## Para que serve
8
-
9
- Use esta biblioteca para:
10
-
11
- - Extrair comandos embutidos em respostas de IA.
12
- - Capturar varios comandos na mesma mensagem.
13
- - Normalizar saida em um objeto padrao com `type`, `command`, `extra` e `seq`.
14
- - Interpretar entradas com chaves em ingles e portugues (`type/tipo`, `command/comando`).
15
-
16
- Ela **nao executa comandos**. Ela apenas faz parsing.
7
+ - `cmd`
8
+ - `extractFirstCommand`
9
+ - `cursor`
17
10
 
18
11
  ## Instalacao
19
12
 
@@ -21,223 +14,277 @@ Ela **nao executa comandos**. Ela apenas faz parsing.
21
14
  npm install command-cmd
22
15
  ```
23
16
 
24
- ## Uso rapido
17
+ ## Importacao (com nomes em PT-BR)
25
18
 
26
19
  ```js
27
- import { cmd, extractFirstCommand } from 'command-cmd';
20
+ import {
21
+ cmd as extrairComandos,
22
+ extractFirstCommand as extrairPrimeiroComando,
23
+ cursor as executarCursor
24
+ } from 'command-cmd';
25
+ ```
26
+
27
+ ## 1) `cmd(message)`
28
28
 
29
- const textoDaIA = `
30
- Vamos executar em duas etapas:
31
- [tipo: shell, comando: npm test, extra: executar na raiz do projeto, seq: 2]
32
- `;
29
+ Extrai blocos de comando de um texto.
33
30
 
34
- const listaDeComandos = cmd(textoDaIA);
35
- const primeiroComando = extractFirstCommand(textoDaIA);
31
+ Formato aceito:
36
32
 
37
- console.log(listaDeComandos);
38
- console.log(primeiroComando);
33
+ ```txt
34
+ [tipo: abrir_app, comando: tiktok, botao: barra_pesquisa, seq: 1]
39
35
  ```
40
36
 
41
- Saida esperada:
37
+ Para movimento sequencial:
42
38
 
43
- ```js
44
- [
45
- {
46
- type: 'shell',
47
- command: 'npm test',
48
- extra: 'executar na raiz do projeto',
49
- seq: 2
50
- }
51
- ]
39
+ ```txt
40
+ [mover_sequencia, points: 120x220|420x260|800x300, interval: 300, click: between, doubleClick: true]
41
+ ```
52
42
 
43
+ Tambem aceita o formato curto que voce pediu:
44
+
45
+ ```txt
46
+ [abrir_app, comando: tiktok, botao: pularADS]
47
+ ```
48
+
49
+ Objeto retornado:
50
+
51
+ ```js
53
52
  {
54
- type: 'shell',
55
- command: 'npm test',
56
- extra: 'executar na raiz do projeto',
57
- seq: 2
53
+ type: 'abrir_app',
54
+ command: 'tiktok',
55
+ extra: undefined,
56
+ button: 'pularADS',
57
+ seq: 1
58
58
  }
59
59
  ```
60
60
 
61
- ## API completa
61
+ ## 2) `extractFirstCommand(message)`
62
62
 
63
- ### `cmd(message)`
63
+ Retorna apenas o primeiro comando valido.
64
64
 
65
- Extrai **todos** os comandos validos de uma string.
65
+ Saida:
66
66
 
67
- - Entrada: `message` (string).
68
- - Saida: `Array<{ type: string, command: string, extra?: string, seq?: number }>`
69
- - Se `message` nao for string: retorna `[]`.
70
- - Se nenhum comando valido for encontrado: retorna `[]`.
67
+ ```js
68
+ { type, command, extra, button, points, path, interval, clickDelay, click, doubleClick, seq } | null
69
+ ```
71
70
 
72
- Um comando so entra no resultado quando tem:
71
+ ## 3) `cursor(input, options?)`
73
72
 
74
- - `type` (ou `tipo`)
75
- - `command` (ou `comando`)
73
+ Executa os comandos com mouse/teclado.
76
74
 
77
- Regras do campo `seq`:
75
+ `input` pode ser:
78
76
 
79
- - E opcional.
80
- - Aceita apenas numero inteiro maior ou igual a zero.
81
- - Se o valor for invalido (ex.: `seq: duas_vezes`), o campo fica `undefined`.
77
+ - `string` (a funcao chama `cmd` internamente)
78
+ - objeto unico
79
+ - lista de objetos
82
80
 
83
- ### `extractFirstCommand(message)`
81
+ Campos aceitos no objeto:
84
82
 
85
- Retorna apenas o primeiro comando valido.
83
+ - `type` ou `tipo`
84
+ - `command` ou `comando`
85
+ - `button` ou `botao`
86
+ - `points` ou `pontos` (sequencia de coordenadas)
87
+ - `path` ou `caminho` ou `trajeto` (alias de `points`)
88
+ - `interval` ou `intervalo` (ms entre pontos)
89
+ - `clickDelay` ou `delayClique` (ms entre chegada no ponto e clique)
90
+ - `click` ou `clique` (`none`, `between`, `each`, `first`, `last`)
91
+ - `doubleClick` ou `duplo` (true/false)
92
+ - `seq` ou `sequencia`
93
+ - `extra`
86
94
 
87
- - Entrada: `message` (string).
88
- - Saida: `{ type, command, extra, seq } | null`
89
- - Se nao houver comando valido: retorna `null`.
95
+ ### Nome dos apps e botoes (importante)
90
96
 
91
- ## Formato esperado dos comandos
97
+ Voce pode chamar apps e botoes como quiser.
92
98
 
93
- Cada comando deve estar entre colchetes:
99
+ A lib usa o nome apenas para buscar no `map.json`, aplicando normalizacao:
94
100
 
95
- ```txt
96
- [tipo: valor, comando: valor, extra: valor, seq: 3]
97
- ```
101
+ - ignora maiusculas/minusculas;
102
+ - converte espaco/hifen para `_`;
103
+ - remove acentos.
98
104
 
99
- Chaves reconhecidas:
105
+ Exemplos que viram a mesma chave logica:
100
106
 
101
- - `type` ou `tipo`
102
- - `command` ou `comando`
103
- - `extra`
104
- - `seq`
107
+ - `Pular ADS`
108
+ - `pular-ads`
109
+ - `pular_ads`
105
110
 
106
- A comparacao das chaves e case-insensitive.
111
+ O mesmo vale para nome de app:
107
112
 
108
- Exemplos validos:
113
+ - `Tik Tok`
114
+ - `tik-tok`
115
+ - `tik_tok`
109
116
 
110
- ```txt
111
- [TYPE: shell, COMMAND: ls -la, SEQ: 1]
112
- [tipo: shell, comando: pwd, seq: 2]
117
+ Exemplo com objeto em PT-BR:
118
+
119
+ ```js
120
+ const listaDeComandos = [
121
+ { tipo: 'abrir_app', comando: 'youtube', botao: 'barra_pesquisa', seq: 1 },
122
+ { tipo: 'abrir_app', comando: 'youtube', botao: 'pularADS', seq: 1 }
123
+ ];
124
+
125
+ const resumoDeExecucao = await executarCursor(listaDeComandos);
126
+ console.log(resumoDeExecucao);
113
127
  ```
114
128
 
115
- ## Regras de parsing
129
+ ## Estado de app (aberto/fechado)
116
130
 
117
- - A biblioteca procura trechos no formato `[ ... ]`.
118
- - O conteudo interno e dividido por virgula.
119
- - Cada parte precisa ter `:` para virar `chave: valor`.
120
- - Bloco sem `type/tipo` ou `command/comando` e ignorado.
121
- - `seq` so e aplicado quando for numero inteiro nao negativo.
122
- - Chaves desconhecidas usam fallback:
123
- - A primeira chave desconhecida pode virar `type`, e o valor vira `command`.
124
- - Chaves desconhecidas seguintes podem preencher `extra`, se ainda estiver vazio.
131
+ Para comandos de app (`abrir_app`, `close_app`), a lib usa `map.json`:
125
132
 
126
- ## Exemplos com respostas de IA
133
+ 1. Le o estado atual do app (`aberto` ou `fechado`).
134
+ 2. Se ja estiver `aberto`, nao tenta abrir de novo.
135
+ 3. Executa o clique no botao mapeado.
136
+ 4. Se o botao tiver `setState`, atualiza o estado no `map.json`.
127
137
 
128
- ### Exemplo 1: comando unico
138
+ ### Criacao automatica do `map.json`
129
139
 
130
- Texto da IA:
140
+ Se o arquivo `map.json` nao existir, a lib cria automaticamente na raiz do projeto (cwd), por padrao.
131
141
 
132
- ```txt
133
- Para listar arquivos:
134
- [tipo: shell, comando: ls -la, extra: linux ou mac, seq: 1]
135
- ```
142
+ Esse arquivo inicial ja vem com:
136
143
 
137
- Resultado:
144
+ - `state` em cada app;
145
+ - `buttons` em cada app;
146
+ - botao `fechar` padrao em cada app (`setState: "fechado"`).
138
147
 
139
- ```js
140
- [
141
- {
142
- type: 'shell',
143
- command: 'ls -la',
144
- extra: 'linux ou mac',
145
- seq: 1
146
- }
147
- ]
148
- ```
148
+ Isso permite cenarios como:
149
149
 
150
- ### Exemplo 2: varios comandos
150
+ - "clique na barra de pesquisa do TikTok"
151
+ - "clique no botao de pular anuncio do YouTube"
151
152
 
152
- Texto da IA:
153
+ A IA pode converter para:
153
154
 
154
155
  ```txt
155
- Passo 1: [tipo: shell, comando: npm install, seq: 1]
156
- Passo 2: [tipo: shell, comando: npm run dev, extra: porta padrao 3000, seq: 1]
157
- Passo 3: [tipo: shell, comando: npm test, seq: 3]
156
+ [abrir_app, comando: tiktok, botao: barra_pesquisa]
157
+ [abrir_app, comando: youtube, botao: pularADS]
158
158
  ```
159
159
 
160
- Resultado:
160
+ ## Comandos de `type` suportados
161
161
 
162
- ```js
163
- [
164
- { type: 'shell', command: 'npm install', extra: undefined, seq: 1 },
165
- { type: 'shell', command: 'npm run dev', extra: 'porta padrao 3000', seq: 1 },
166
- { type: 'shell', command: 'npm test', extra: undefined, seq: 3 }
167
- ]
168
- ```
162
+ - `abrir_app` / `open_app`
163
+ - `fechar_app` / `close_app`
164
+ - `click` / `clicar`
165
+ - `double_click` / `clique_duplo`
166
+ - `scroll` / `rolar`
167
+ - `mover_sequencia` / `move_sequence`
168
+ - `skip_ads` / `pular_ads`
169
+
170
+ ## Movimento sequencial (novo)
169
171
 
170
- ### Exemplo 3: `seq` invalido
172
+ Use `mover_sequencia` para mover o mouse por 2 ou mais coordenadas em ordem.
171
173
 
172
- Texto da IA:
174
+ ### Formato de coordenadas
175
+
176
+ Use `points` no formato:
173
177
 
174
178
  ```txt
175
- [tipo: shell, comando: npm test, seq: tres]
179
+ 100x200|300x260|640x420
176
180
  ```
177
181
 
178
- Resultado:
182
+ Tambem funciona com `:`:
179
183
 
180
- ```js
181
- [
182
- {
183
- type: 'shell',
184
- command: 'npm test',
185
- extra: undefined,
186
- seq: undefined
187
- }
188
- ]
184
+ ```txt
185
+ 100:200|300:260|640:420
189
186
  ```
190
187
 
191
- ## Como construir o system prompt da IA
188
+ ### Controle de clique entre pontos
192
189
 
193
- A qualidade da extracao depende de um formato estavel. O ideal e forcar o modelo a sempre emitir blocos validos.
190
+ Campo `click`:
194
191
 
195
- ### Modelo 1: estrito (somente blocos)
192
+ - `none`: sem clique
193
+ - `between`: clica em cada ponto antes do ultimo
194
+ - `each`: clica em todos os pontos
195
+ - `first`: clica so no primeiro ponto
196
+ - `last`: clica so no ultimo ponto
196
197
 
197
- ```txt
198
- Voce e um assistente tecnico.
199
- Sempre que sugerir uma acao executavel, responda usando blocos no formato:
200
- [tipo: <categoria>, comando: <comando>, extra: <contexto opcional>, seq: <inteiro opcional>]
198
+ Campo `doubleClick`:
201
199
 
202
- Regras obrigatorias:
203
- 1. Use sempre colchetes.
204
- 2. Use as chaves exatamente como: tipo, comando, extra, seq.
205
- 3. seq deve ser inteiro >= 0.
206
- 4. Nao use virgula dentro dos valores.
207
- 5. Um comando por bloco.
200
+ - `true`: usa clique duplo quando houver clique
201
+ - `false`: clique simples
202
+
203
+ Campo `clickDelay`:
204
+
205
+ - delay (ms) entre chegada no ponto e clique naquele ponto
206
+
207
+ ### Exemplo em texto da IA
208
+
209
+ ```txt
210
+ [mover_sequencia, points: 140x260|520x280|980x300, interval: 250, click: between, doubleClick: true]
208
211
  ```
209
212
 
210
- ### Modelo 2: explicacao + blocos
213
+ Com `clickDelay`:
211
214
 
212
215
  ```txt
213
- Voce pode explicar em linguagem natural.
214
- Quando houver comando, inclua um bloco por comando no formato:
215
- [tipo: <categoria>, comando: <comando>, extra: <contexto opcional>, seq: <inteiro opcional>]
216
+ [mover_sequencia, caminho: 140x260|520x280|980x300, interval: 250, clickDelay: 120, click: between, doubleClick: true]
217
+ ```
218
+
219
+ ### Exemplo em objeto JS (mais facil para o dev)
216
220
 
217
- Se nao houver comando, nao invente bloco.
218
- Se houver repeticao, informe em seq.
221
+ ```js
222
+ await executarCursor({
223
+ tipo: 'mover_sequencia',
224
+ pontos: [
225
+ { x: 140, y: 260 },
226
+ { x: 520, y: 280 },
227
+ { x: 980, y: 300 }
228
+ ],
229
+ intervalo: 250,
230
+ delayClique: 120,
231
+ clique: 'between',
232
+ duplo: true
233
+ });
219
234
  ```
220
235
 
221
- ### Exemplo de resposta esperada da IA
236
+ ## Opcoes de `cursor`
222
237
 
223
- ```txt
224
- Para preparar o ambiente, siga:
225
- [tipo: shell, comando: npm install, extra: instalar dependencias, seq: 1]
226
- [tipo: shell, comando: npm test, extra: repetir para validar estabilidade, seq: 3]
238
+ ```js
239
+ {
240
+ mapPath: 'map.json', // caminho do mapeador
241
+ createMapIfMissing: true, // padrao: cria map automaticamente se faltar
242
+ persistMap: true, // salva estado no arquivo
243
+ launchKey: 'command', // use false para nao disparar tecla de launcher
244
+ moveSteps: 50,
245
+ moveDelay: 8,
246
+ scrollSteps: 10,
247
+ scrollDelay: 20,
248
+ typeDelay: 50,
249
+ sequenceInterval: 250, // intervalo padrao do mover_sequencia
250
+ sequenceClick: 'none', // none|between|each|first|last
251
+ sequenceDoubleClick: false, // clique duplo por padrao
252
+ defaultClosePoint: { x: 1888, y: 16 } // botao "fechar" padrao
253
+ }
227
254
  ```
228
255
 
229
- ## Limitacoes atuais
256
+ ## map.json (doc dedicada)
257
+
258
+ Voce pediu uma doc dedicada para o mapeador. Ela esta aqui:
230
259
 
231
- - Valores com virgula podem quebrar o parsing porque a divisao interna usa `,`.
232
- - Colchetes dentro do valor (`[` ou `]`) podem interferir na captura.
233
- - Apenas um campo `extra` e retornado por bloco.
260
+ - `mapPTBR.md` (PT-BR)
261
+ - `map.md` (English)
234
262
 
235
- ## Seguranca
263
+ Esses arquivos explicam:
236
264
 
237
- Esta biblioteca nao executa comandos.
265
+ - como montar `apps`, `buttons` e `state`;
266
+ - como mapear coordenadas;
267
+ - como mapear botoes por app;
268
+ - como usar `setState` para atualizar para `fechado`.
269
+
270
+ ## System prompt recomendado (IA -> comando)
271
+
272
+ ```txt
273
+ Voce e um assistente tecnico.
274
+ Quando sugerir acao executavel, responda apenas com blocos:
275
+ [tipo: <tipo>, comando: <app_ou_valor>, points: <pontos_opcionais>, botao: <botao_opcional>, interval: <ms_opcional>, clickDelay: <ms_ate_click>, click: <modo_opcional>, doubleClick: <boolean_opcional>, extra: <texto_opcional>, seq: <inteiro_opcional>]
276
+
277
+ Regras:
278
+ 1. Use sempre colchetes.
279
+ 2. Um comando por bloco.
280
+ 3. seq deve ser inteiro >= 0.
281
+ 4. Para app, use tipo abrir_app e comando com nome do app.
282
+ 5. Quando for clique de elemento de UI, preencha botao com a chave do map.json.
283
+ 6. Para movimento sequencial, use tipo mover_sequencia e `points` no formato `xXy|xXy|xXy`.
284
+ ```
238
285
 
239
- Se voce for executar o resultado extraido:
286
+ ## Observacoes importantes
240
287
 
241
- - Use allowlist de comandos permitidos.
242
- - Bloqueie padroes perigosos.
243
- - Exija confirmacao para operacoes destrutivas.
288
+ - `cursor` precisa de ambiente desktop com `robotjs`.
289
+ - Se `map.json` nao existir e `createMapIfMissing` for `false`, comandos de app retornam erro.
290
+ - Evite virgulas dentro dos valores do bloco `[ ... ]`, porque o parser divide por virgula.
package/map.md ADDED
@@ -0,0 +1,238 @@
1
+ # map.json (Mapper Guide - English)
2
+
3
+ This guide explains how to create `map.json` so the library knows:
4
+
5
+ - where to click to open each app;
6
+ - where app-specific buttons are;
7
+ - current app state (`open` / `closed` or `aberto` / `fechado`).
8
+
9
+ Note: `move_sequence` does not depend on `map.json`; it uses coordinates from the command payload.
10
+
11
+ ## File location
12
+
13
+ Create `map.json` in your project root (where Node runs).
14
+
15
+ By default, the library auto-creates this file if it does not exist.
16
+
17
+ If you need another path:
18
+
19
+ ```js
20
+ await cursor(commands, { mapPath: './config/map.json' });
21
+ ```
22
+
23
+ ## Minimal structure
24
+
25
+ ```json
26
+ {
27
+ "version": 1,
28
+ "apps": {
29
+ "youtube": {
30
+ "state": "closed",
31
+ "launcher": { "x": 20, "y": 225 },
32
+ "buttons": {}
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ Even with minimal setup, the generated default map includes:
39
+
40
+ - `state`;
41
+ - `buttons`;
42
+ - a default `close/fechar` button per app (`setState: "fechado"`).
43
+
44
+ ## Recommended structure (with buttons)
45
+
46
+ ```json
47
+ {
48
+ "version": 1,
49
+ "apps": {
50
+ "tiktok": {
51
+ "state": "closed",
52
+ "launcher": {
53
+ "position": { "x": 28, "y": 260 },
54
+ "path": {
55
+ "points": ["60x740", "140x740", "220x740", "28x260"],
56
+ "click": "between",
57
+ "interval": 180,
58
+ "clickDelay": 120
59
+ }
60
+ },
61
+ "searchIcon": { "x": 612, "y": 138 },
62
+ "buttons": {
63
+ "search_bar": { "x": 612, "y": 138 }
64
+ }
65
+ },
66
+ "youtube": {
67
+ "state": "open",
68
+ "launcher": { "x": 20, "y": 225 },
69
+ "searchIcon": { "x": 575, "y": 144 },
70
+ "buttons": {
71
+ "skip_ads": { "x": 808, "y": 569 },
72
+ "close": {
73
+ "position": { "x": 1888, "y": 16 },
74
+ "setState": "closed"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## App fields
83
+
84
+ - `state`: `open`, `closed`, `aberto`, or `fechado`
85
+ - `launcher`: click point used to open/focus app
86
+ - `searchIcon`: search icon point (optional)
87
+ - `buttons`: per-app button map
88
+ - optional `path/caminho` inside `launcher`: movement route before final click
89
+
90
+ ## Button fields
91
+
92
+ Simple format:
93
+
94
+ ```json
95
+ "skip_ads": { "x": 808, "y": 569 }
96
+ ```
97
+
98
+ Full format:
99
+
100
+ ```json
101
+ "close": {
102
+ "position": { "x": 1888, "y": 16 },
103
+ "setState": "closed"
104
+ }
105
+ ```
106
+
107
+ With path:
108
+
109
+ ```json
110
+ "search_bar": {
111
+ "position": { "x": 612, "y": 138 },
112
+ "path": {
113
+ "points": ["500x140", "560x140", "612x138"],
114
+ "click": "between",
115
+ "interval": 150,
116
+ "clickDelay": 100,
117
+ "doubleClick": false
118
+ }
119
+ }
120
+ ```
121
+
122
+ When `setState` is present, the library updates app state in `map.json` after click.
123
+
124
+ ## App and button names
125
+
126
+ You can name buttons freely in `map.json`.
127
+
128
+ Same for app names inside `apps`.
129
+
130
+ Example:
131
+
132
+ ```json
133
+ "buttons": {
134
+ "my_custom_button": { "x": 500, "y": 300 }
135
+ }
136
+ ```
137
+
138
+ Then call it using:
139
+
140
+ ```txt
141
+ [open_app, command: tiktok, button: my_custom_button]
142
+ ```
143
+
144
+ Lookup uses normalization (case-insensitive, spaces/hyphens to `_`, accent-insensitive).
145
+
146
+ ## Optional `path/caminho` field
147
+
148
+ You can use `path/caminho` in:
149
+
150
+ - `apps.<name>.launcher`
151
+ - `apps.<name>.searchIcon`
152
+ - `apps.<name>.buttons.<buttonName>`
153
+
154
+ Format:
155
+
156
+ ```json
157
+ "path": {
158
+ "points": ["100x200", "300x260", "640x420"],
159
+ "click": "between",
160
+ "interval": 200,
161
+ "clickDelay": 120,
162
+ "doubleClick": false
163
+ }
164
+ ```
165
+
166
+ Fields:
167
+
168
+ - `points`: coordinate sequence
169
+ - `click`: `none`, `between`, `each`, `first`, `last`
170
+ - `interval`: delay between points (ms)
171
+ - `clickDelay`: delay between arriving and clicking at that point (ms)
172
+ - `doubleClick`: whether path clicks are double clicks
173
+
174
+ Note:
175
+
176
+ - after the path, the library still performs the final click on `position`.
177
+
178
+ ## How app state is used
179
+
180
+ Command:
181
+
182
+ ```txt
183
+ [open_app, command: youtube, button: skipADS]
184
+ ```
185
+
186
+ Flow:
187
+
188
+ 1. Reads `apps.youtube.state`.
189
+ 2. If already open, it does not open again.
190
+ 3. Resolves `buttons.skip_ads` and clicks.
191
+ 4. If that button has `setState`, it writes the new state to file.
192
+
193
+ ## AI command examples
194
+
195
+ ```txt
196
+ [open_app, command: tiktok, button: search_bar]
197
+ [open_app, command: youtube, button: skipADS]
198
+ [close_app, command: youtube, button: close]
199
+ ```
200
+
201
+ ## Accepted aliases
202
+
203
+ App:
204
+
205
+ - `google` -> `chrome`
206
+
207
+ Button:
208
+
209
+ - `skipADS` -> `skip_ads`
210
+ - `search`, `search_bar`, `barra_pesquisa` -> same logical key
211
+ - `close`, `fechar` -> `close`/`fechar` mapping
212
+
213
+ ## Quick coordinate calibration
214
+
215
+ Use this script to read current mouse coordinates:
216
+
217
+ ```js
218
+ import robot from 'robotjs';
219
+
220
+ setInterval(() => {
221
+ const p = robot.getMousePos();
222
+ console.log(`x=${p.x}, y=${p.y}`);
223
+ }, 300);
224
+ ```
225
+
226
+ Move mouse over launcher/buttons and copy values into `map.json`.
227
+
228
+ ## Common errors
229
+
230
+ - `unknown_app:<name>`: app not found in `map.json`
231
+ - `unknown_button:<app>:<button>`: missing button under `apps.<app>.buttons`
232
+ - `missing_launcher:<app>`: app has no `launcher`
233
+ - `map_not_found:<path>`: map file not found
234
+
235
+ ## Maintenance tip
236
+
237
+ Keep stable button keys (`search_bar`, `skip_ads`, `close`).
238
+ If UI changes, update only `map.json`, not your code.