command-cmd 1.0.4 → 1.0.6

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/cmd.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { fileURLToPath } from 'url';
3
4
 
4
- const { readFile, writeFile } = fs.promises;
5
- const { resolve: resolvePath } = path;
5
+ const { readFile, writeFile, mkdir } = fs.promises;
6
+ const { resolve: resolvePath, dirname: dirnamePath } = path;
7
+
8
+ const MODULE_DIR = dirnamePath(fileURLToPath(import.meta.url));
6
9
 
7
10
  const DEFAULT_APPS = Object.freeze({
8
11
  spotify: {
@@ -19,6 +22,35 @@ const DEFAULT_APPS = Object.freeze({
19
22
  }
20
23
  });
21
24
 
25
+ const DOC_TEMPLATE_MAP = Object.freeze({
26
+ doc_en: Object.freeze({
27
+ key: 'doc_en',
28
+ source: 'doc.md',
29
+ target: 'doc.md',
30
+ isMapDoc: false
31
+ }),
32
+ doc_ptbr: Object.freeze({
33
+ key: 'doc_ptbr',
34
+ source: 'docPTBR.md',
35
+ target: 'docPTBR.md',
36
+ isMapDoc: false
37
+ }),
38
+ map_en: Object.freeze({
39
+ key: 'map_en',
40
+ source: 'map.md',
41
+ target: 'map.md',
42
+ isMapDoc: true
43
+ }),
44
+ map_ptbr: Object.freeze({
45
+ key: 'map_ptbr',
46
+ source: 'mapPTBR.md',
47
+ target: 'mapPTBR.md',
48
+ isMapDoc: true
49
+ })
50
+ });
51
+
52
+ const DOC_TEMPLATES = Object.freeze(Object.values(DOC_TEMPLATE_MAP));
53
+
22
54
  const ACTION_ALIASES = Object.freeze({
23
55
  skip_ads: 'skip_ads',
24
56
  skipads: 'skip_ads',
@@ -45,7 +77,20 @@ const ACTION_ALIASES = Object.freeze({
45
77
  close_app: 'close_app',
46
78
  closeapp: 'close_app',
47
79
  fechar_app: 'close_app',
48
- fecharapp: 'close_app'
80
+ fecharapp: 'close_app',
81
+ key_tap: 'key_tap',
82
+ keytap: 'key_tap',
83
+ tap_key: 'key_tap',
84
+ tapkey: 'key_tap',
85
+ press_key: 'key_tap',
86
+ pressionar_tecla: 'key_tap',
87
+ tecla: 'key_tap',
88
+ type_string_delayed: 'type_string_delayed',
89
+ typestringdelayed: 'type_string_delayed',
90
+ type_string: 'type_string_delayed',
91
+ typestring: 'type_string_delayed',
92
+ digitar: 'type_string_delayed',
93
+ escrever: 'type_string_delayed'
49
94
  });
50
95
 
51
96
  const BUTTON_ALIASES = Object.freeze({
@@ -84,6 +129,10 @@ const CLICK_MODE = Object.freeze({
84
129
  });
85
130
 
86
131
  let robotCache;
132
+ const MAPER_POLL_INTERVAL = 50;
133
+ let maperInterval = null;
134
+ let maperLastPosition = null;
135
+ let maperPrintedLine = false;
87
136
 
88
137
  export function cmd(message) {
89
138
  if (typeof message !== 'string') {
@@ -103,6 +152,10 @@ export function cmd(message) {
103
152
  command: undefined,
104
153
  extra: undefined,
105
154
  button: undefined,
155
+ key: undefined,
156
+ modifiers: undefined,
157
+ text: undefined,
158
+ typeDelay: undefined,
106
159
  points: undefined,
107
160
  path: undefined,
108
161
  interval: undefined,
@@ -140,6 +193,42 @@ export function cmd(message) {
140
193
  command.extra = value;
141
194
  } else if (key === 'button' || key === 'botao' || key === 'botão') {
142
195
  command.button = value;
196
+ } else if (key === 'key' || key === 'tecla' || key === 'hotkey') {
197
+ command.key = value;
198
+ if (!command.command) {
199
+ command.command = value;
200
+ }
201
+ } else if (
202
+ key === 'modifier' ||
203
+ key === 'modifiers' ||
204
+ key === 'modificador' ||
205
+ key === 'modificadores'
206
+ ) {
207
+ command.modifiers = value;
208
+ } else if (
209
+ key === 'text' ||
210
+ key === 'texto' ||
211
+ key === 'string' ||
212
+ key === 'mensagem'
213
+ ) {
214
+ command.text = value;
215
+ if (!command.command) {
216
+ command.command = value;
217
+ }
218
+ } else if (
219
+ key === 'typedelay' ||
220
+ key === 'type_delay' ||
221
+ key === 'typingdelay' ||
222
+ key === 'typing_delay' ||
223
+ key === 'textdelay' ||
224
+ key === 'delaytexto' ||
225
+ key === 'delaydigitacao' ||
226
+ key === 'delaydigitar'
227
+ ) {
228
+ const typeDelay = toNonNegativeInteger(value);
229
+ if (typeDelay !== null) {
230
+ command.typeDelay = typeDelay;
231
+ }
143
232
  } else if (
144
233
  key === 'points' ||
145
234
  key === 'pontos' ||
@@ -200,7 +289,7 @@ export function cmd(message) {
200
289
  }
201
290
  }
202
291
 
203
- if (command.type && command.command) {
292
+ if (hasCommandPayload(command)) {
204
293
  results.push(command);
205
294
  }
206
295
  }
@@ -213,6 +302,161 @@ export function extractFirstCommand(message) {
213
302
  return first || null;
214
303
  }
215
304
 
305
+ export async function maper(enabled) {
306
+ const shouldEnable = parseBooleanValue(enabled);
307
+ if (shouldEnable === null) {
308
+ throw new Error('maper_requires_boolean_true_or_false');
309
+ }
310
+
311
+ if (!shouldEnable) {
312
+ if (maperInterval) {
313
+ clearInterval(maperInterval);
314
+ maperInterval = null;
315
+ }
316
+
317
+ maperLastPosition = null;
318
+
319
+ if (maperPrintedLine && process.stdout && typeof process.stdout.write === 'function') {
320
+ process.stdout.write('\n');
321
+ }
322
+
323
+ maperPrintedLine = false;
324
+ return { active: false, pollInterval: MAPER_POLL_INTERVAL };
325
+ }
326
+
327
+ const robot = await getRobot();
328
+
329
+ const printPosition = (position) => {
330
+ const line = `[maper] x: ${position.x} | y: ${position.y}`;
331
+
332
+ if (process.stdout && process.stdout.isTTY && typeof process.stdout.write === 'function') {
333
+ process.stdout.write(`\r${line}`);
334
+ maperPrintedLine = true;
335
+ return;
336
+ }
337
+
338
+ console.log(line);
339
+ };
340
+
341
+ const pollPosition = () => {
342
+ let position;
343
+ try {
344
+ position = robot.getMousePos();
345
+ } catch (error) {
346
+ return;
347
+ }
348
+
349
+ if (!position || !Number.isFinite(position.x) || !Number.isFinite(position.y)) {
350
+ return;
351
+ }
352
+
353
+ if (
354
+ maperLastPosition &&
355
+ position.x === maperLastPosition.x &&
356
+ position.y === maperLastPosition.y
357
+ ) {
358
+ return;
359
+ }
360
+
361
+ maperLastPosition = { x: position.x, y: position.y };
362
+ printPosition(position);
363
+ };
364
+
365
+ if (maperInterval) {
366
+ return { active: true, pollInterval: MAPER_POLL_INTERVAL };
367
+ }
368
+
369
+ maperLastPosition = null;
370
+ pollPosition();
371
+ maperInterval = setInterval(pollPosition, MAPER_POLL_INTERVAL);
372
+
373
+ if (typeof maperInterval.unref === 'function') {
374
+ maperInterval.unref();
375
+ }
376
+
377
+ return { active: true, pollInterval: MAPER_POLL_INTERVAL };
378
+ }
379
+
380
+ export async function initMap(options = {}) {
381
+ const config = buildCursorConfig(options);
382
+ const mapAlreadyExists = await pathExists(config.mapPath);
383
+ const store = await createMapStore(config);
384
+
385
+ const persistMerged =
386
+ parseBooleanValue(options && options.persistMerged) === true ||
387
+ parseBooleanValue(options && options.writeMerged) === true;
388
+
389
+ if (!mapAlreadyExists && config.persistMap) {
390
+ // already persisted by createMapStore when file is missing
391
+ } else if (persistMerged && config.persistMap) {
392
+ await persistMapStore(store);
393
+ }
394
+
395
+ return {
396
+ path: store.path,
397
+ created: !mapAlreadyExists,
398
+ persisted: (!mapAlreadyExists || persistMerged) && config.persistMap,
399
+ version: store.map.version,
400
+ appCount: Object.keys(store.map.apps || {}).length,
401
+ map: store.map
402
+ };
403
+ }
404
+
405
+ export async function initDoc(options = {}) {
406
+ const raw = options && typeof options === 'object' ? options : {};
407
+ const outputDir = resolveDocOutputDir(raw);
408
+ const overwrite = parseBooleanValue(raw.overwrite) === true;
409
+ const includeMapDocs = shouldIncludeMapDocs(raw.includeMapDocs);
410
+ const templates = resolveDocTemplates(raw.files, includeMapDocs);
411
+
412
+ await mkdir(outputDir, { recursive: true });
413
+
414
+ const created = [];
415
+ const updated = [];
416
+ const skipped = [];
417
+
418
+ for (const template of templates) {
419
+ const sourcePath = resolvePath(MODULE_DIR, template.source);
420
+ const targetPath = resolvePath(outputDir, template.target);
421
+ const targetExists = await pathExists(targetPath);
422
+
423
+ if (targetExists && !overwrite) {
424
+ skipped.push({
425
+ key: template.key,
426
+ path: targetPath,
427
+ reason: 'exists'
428
+ });
429
+ continue;
430
+ }
431
+
432
+ let content;
433
+ try {
434
+ content = await readFile(sourcePath, 'utf8');
435
+ } catch (error) {
436
+ if (error && error.code === 'ENOENT') {
437
+ throw new Error(`doc_template_not_found:${template.source}`);
438
+ }
439
+ throw error;
440
+ }
441
+
442
+ await writeFile(targetPath, content, 'utf8');
443
+
444
+ if (targetExists) {
445
+ updated.push({ key: template.key, path: targetPath });
446
+ } else {
447
+ created.push({ key: template.key, path: targetPath });
448
+ }
449
+ }
450
+
451
+ return {
452
+ outputDir,
453
+ overwrite,
454
+ created,
455
+ updated,
456
+ skipped
457
+ };
458
+ }
459
+
216
460
  export async function cursor(input, options = {}) {
217
461
  const commands = normalizeCursorInput(input);
218
462
  if (commands.length === 0) {
@@ -313,6 +557,10 @@ export async function cursor(input, options = {}) {
313
557
  command: command.command,
314
558
  extra: command.extra,
315
559
  button: command.button,
560
+ key: command.key,
561
+ modifiers: command.modifiers,
562
+ text: command.text,
563
+ typeDelay: command.typeDelay,
316
564
  points: command.points,
317
565
  path: command.path,
318
566
  interval: command.interval,
@@ -428,6 +676,45 @@ function mergeOptionApps(customApps) {
428
676
  return merged;
429
677
  }
430
678
 
679
+ function toCommandText(value) {
680
+ if (typeof value === 'string') {
681
+ const trimmed = value.trim();
682
+ return trimmed || '';
683
+ }
684
+
685
+ if (Number.isFinite(value)) {
686
+ return String(value);
687
+ }
688
+
689
+ return '';
690
+ }
691
+
692
+ function hasCommandPayload(command) {
693
+ if (!command || !command.type) {
694
+ return false;
695
+ }
696
+
697
+ const actionKey = resolveActionKey(command.type);
698
+
699
+ if (actionKey === 'key_tap') {
700
+ return Boolean(toCommandText(command.key) || toCommandText(command.command));
701
+ }
702
+
703
+ if (actionKey === 'type_string_delayed') {
704
+ return Boolean(
705
+ toCommandText(command.text) ||
706
+ toCommandText(command.command) ||
707
+ toCommandText(command.extra)
708
+ );
709
+ }
710
+
711
+ if (actionKey === 'move_sequence') {
712
+ return Boolean(command.points || command.path || toCommandText(command.command));
713
+ }
714
+
715
+ return Boolean(toCommandText(command.command));
716
+ }
717
+
431
718
  function normalizeCommandObject(value) {
432
719
  if (!value || typeof value !== 'object') {
433
720
  return null;
@@ -435,6 +722,28 @@ function normalizeCommandObject(value) {
435
722
 
436
723
  const rawType = value.type !== undefined ? value.type : value.tipo;
437
724
  const rawCommand = value.command !== undefined ? value.command : value.comando;
725
+ const rawKey =
726
+ value.key !== undefined
727
+ ? value.key
728
+ : value.tecla !== undefined
729
+ ? value.tecla
730
+ : value.hotkey;
731
+ const rawModifiers =
732
+ value.modifiers !== undefined
733
+ ? value.modifiers
734
+ : value.modifier !== undefined
735
+ ? value.modifier
736
+ : value.modificadores !== undefined
737
+ ? value.modificadores
738
+ : value.modificador;
739
+ const rawText =
740
+ value.text !== undefined
741
+ ? value.text
742
+ : value.texto !== undefined
743
+ ? value.texto
744
+ : value.string !== undefined
745
+ ? value.string
746
+ : value.mensagem;
438
747
  const rawButton =
439
748
  value.button !== undefined
440
749
  ? value.button
@@ -482,37 +791,66 @@ function normalizeCommandObject(value) {
482
791
  : value.cliqueDuplo !== undefined
483
792
  ? value.cliqueDuplo
484
793
  : value.clique_duplo;
794
+ const rawTypeDelay =
795
+ value.typeDelay !== undefined
796
+ ? value.typeDelay
797
+ : value.type_delay !== undefined
798
+ ? value.type_delay
799
+ : value.typingDelay !== undefined
800
+ ? value.typingDelay
801
+ : value.typing_delay !== undefined
802
+ ? value.typing_delay
803
+ : value.textDelay !== undefined
804
+ ? value.textDelay
805
+ : value.delayTexto !== undefined
806
+ ? value.delayTexto
807
+ : value.delayDigitacao !== undefined
808
+ ? value.delayDigitacao
809
+ : value.delayDigitar;
485
810
 
486
811
  const type =
487
812
  typeof rawType === 'string' ? rawType.trim() : String(rawType || '').trim();
813
+ const actionKey = resolveActionKey(type);
488
814
 
489
815
  const normalizedPoints = normalizePointsValue(
490
816
  rawPoints !== undefined ? rawPoints : rawPath
491
817
  );
492
818
 
493
- let command =
494
- typeof rawCommand === 'string'
495
- ? rawCommand.trim()
496
- : Number.isFinite(rawCommand)
497
- ? String(rawCommand)
498
- : '';
819
+ const key = toCommandText(rawKey);
820
+ const text = toCommandText(rawText);
821
+
822
+ let command = toCommandText(rawCommand);
499
823
 
500
824
  if (!command && normalizedPoints) {
501
825
  command = serializePoints(normalizedPoints);
502
826
  }
503
827
 
504
- if (!type || !command) {
505
- return null;
828
+ if (!command && text) {
829
+ command = text;
830
+ }
831
+
832
+ if (!command && key) {
833
+ command = key;
834
+ }
835
+
836
+ const extra =
837
+ value.extra === undefined || value.extra === null
838
+ ? undefined
839
+ : toCommandText(value.extra) || String(value.extra);
840
+
841
+ if (!command && actionKey === 'type_string_delayed' && extra) {
842
+ command = extra;
506
843
  }
507
844
 
508
845
  const normalized = {
509
846
  type,
510
847
  command,
511
- extra:
512
- value.extra === undefined || value.extra === null
513
- ? undefined
514
- : String(value.extra),
848
+ extra,
515
849
  button: undefined,
850
+ key: undefined,
851
+ modifiers: undefined,
852
+ text: undefined,
853
+ typeDelay: undefined,
516
854
  points: undefined,
517
855
  path: undefined,
518
856
  interval: undefined,
@@ -529,6 +867,24 @@ function normalizeCommandObject(value) {
529
867
  }
530
868
  }
531
869
 
870
+ if (key) {
871
+ normalized.key = key;
872
+ }
873
+
874
+ const modifiers = parseKeyModifiers(rawModifiers);
875
+ if (modifiers && modifiers.length > 0) {
876
+ normalized.modifiers = modifiers;
877
+ }
878
+
879
+ if (text) {
880
+ normalized.text = text;
881
+ }
882
+
883
+ const typeDelay = toNonNegativeInteger(rawTypeDelay);
884
+ if (typeDelay !== null) {
885
+ normalized.typeDelay = typeDelay;
886
+ }
887
+
532
888
  if (normalizedPoints) {
533
889
  normalized.points = normalizedPoints;
534
890
  normalized.path = normalizedPoints;
@@ -562,7 +918,7 @@ function normalizeCommandObject(value) {
562
918
  normalized.seq = seq;
563
919
  }
564
920
 
565
- return normalized;
921
+ return hasCommandPayload(normalized) ? normalized : null;
566
922
  }
567
923
 
568
924
  function normalizeCursorInput(input) {
@@ -896,6 +1252,70 @@ function canonicalToken(value) {
896
1252
  .replace(/[-\s]+/g, '_');
897
1253
  }
898
1254
 
1255
+ function normalizeKeyTapToken(value) {
1256
+ const token = canonicalToken(value);
1257
+ if (!token) {
1258
+ return undefined;
1259
+ }
1260
+
1261
+ if (
1262
+ token === 'cmd' ||
1263
+ token === 'command' ||
1264
+ token === 'meta' ||
1265
+ token === 'super' ||
1266
+ token === 'win' ||
1267
+ token === 'windows'
1268
+ ) {
1269
+ return 'command';
1270
+ }
1271
+
1272
+ if (token === 'ctrl' || token === 'control' || token === 'ctl') {
1273
+ return 'control';
1274
+ }
1275
+
1276
+ if (token === 'option' || token === 'opt') {
1277
+ return 'alt';
1278
+ }
1279
+
1280
+ if (token === 'spacebar') {
1281
+ return 'space';
1282
+ }
1283
+
1284
+ return token;
1285
+ }
1286
+
1287
+ function parseKeyModifiers(value) {
1288
+ if (value === undefined || value === null) {
1289
+ return undefined;
1290
+ }
1291
+
1292
+ const list = Array.isArray(value) ? value : [value];
1293
+ const modifiers = [];
1294
+
1295
+ for (const item of list) {
1296
+ if (item === undefined || item === null) {
1297
+ continue;
1298
+ }
1299
+
1300
+ const chunks =
1301
+ typeof item === 'string'
1302
+ ? item.split(/[+|,\s]+/g)
1303
+ : [String(item)];
1304
+
1305
+ for (const chunk of chunks) {
1306
+ const modifier = normalizeKeyTapToken(chunk);
1307
+ if (!modifier) continue;
1308
+ modifiers.push(modifier);
1309
+ }
1310
+ }
1311
+
1312
+ if (modifiers.length === 0) {
1313
+ return undefined;
1314
+ }
1315
+
1316
+ return Array.from(new Set(modifiers));
1317
+ }
1318
+
899
1319
  function resolveActionKey(type) {
900
1320
  const key = canonicalToken(type);
901
1321
  return ACTION_ALIASES[key];
@@ -1016,6 +1436,124 @@ function cloneAppMap(app) {
1016
1436
  };
1017
1437
  }
1018
1438
 
1439
+ async function pathExists(filePath) {
1440
+ try {
1441
+ await readFile(filePath, 'utf8');
1442
+ return true;
1443
+ } catch (error) {
1444
+ if (error && error.code === 'ENOENT') {
1445
+ return false;
1446
+ }
1447
+ throw error;
1448
+ }
1449
+ }
1450
+
1451
+ function shouldIncludeMapDocs(value) {
1452
+ const parsed = parseBooleanValue(value);
1453
+ if (parsed === null) {
1454
+ return value !== false;
1455
+ }
1456
+ return parsed;
1457
+ }
1458
+
1459
+ function resolveDocTemplateByName(value) {
1460
+ const token = canonicalToken(value);
1461
+ if (!token) {
1462
+ return null;
1463
+ }
1464
+
1465
+ if (
1466
+ token === 'doc' ||
1467
+ token === 'doc_en' ||
1468
+ token === 'docmd' ||
1469
+ token === 'en' ||
1470
+ token === 'english'
1471
+ ) {
1472
+ return DOC_TEMPLATE_MAP.doc_en;
1473
+ }
1474
+
1475
+ if (
1476
+ token === 'doc_ptbr' ||
1477
+ token === 'docptbr' ||
1478
+ token === 'ptbr' ||
1479
+ token === 'pt_br' ||
1480
+ token === 'pt'
1481
+ ) {
1482
+ return DOC_TEMPLATE_MAP.doc_ptbr;
1483
+ }
1484
+
1485
+ if (
1486
+ token === 'map' ||
1487
+ token === 'map_en' ||
1488
+ token === 'mapmd' ||
1489
+ token === 'map_doc' ||
1490
+ token === 'mapdoc'
1491
+ ) {
1492
+ return DOC_TEMPLATE_MAP.map_en;
1493
+ }
1494
+
1495
+ if (
1496
+ token === 'map_ptbr' ||
1497
+ token === 'mapptbr' ||
1498
+ token === 'map_pt' ||
1499
+ token === 'map_ptbr_md'
1500
+ ) {
1501
+ return DOC_TEMPLATE_MAP.map_ptbr;
1502
+ }
1503
+
1504
+ return null;
1505
+ }
1506
+
1507
+ function resolveDocTemplates(filesOption, includeMapDocs) {
1508
+ const defaultTemplates = includeMapDocs
1509
+ ? DOC_TEMPLATES
1510
+ : DOC_TEMPLATES.filter((template) => !template.isMapDoc);
1511
+
1512
+ if (filesOption === undefined || filesOption === null) {
1513
+ return defaultTemplates;
1514
+ }
1515
+
1516
+ const values = Array.isArray(filesOption) ? filesOption : [filesOption];
1517
+ const selected = [];
1518
+ const invalid = [];
1519
+
1520
+ for (const value of values) {
1521
+ const template = resolveDocTemplateByName(value);
1522
+ if (!template) {
1523
+ invalid.push(String(value));
1524
+ continue;
1525
+ }
1526
+ if (!includeMapDocs && template.isMapDoc) {
1527
+ continue;
1528
+ }
1529
+ if (!selected.find((item) => item.key === template.key)) {
1530
+ selected.push(template);
1531
+ }
1532
+ }
1533
+
1534
+ if (selected.length === 0 && invalid.length > 0) {
1535
+ throw new Error(`invalid_doc_templates:${invalid.join('|')}`);
1536
+ }
1537
+
1538
+ return selected.length > 0 ? selected : defaultTemplates;
1539
+ }
1540
+
1541
+ function resolveDocOutputDir(options) {
1542
+ const rawOutput =
1543
+ options.outputDir !== undefined
1544
+ ? options.outputDir
1545
+ : options.dir !== undefined
1546
+ ? options.dir
1547
+ : options.path;
1548
+
1549
+ const output = typeof rawOutput === 'string' ? rawOutput.trim() : '';
1550
+ if (!output) {
1551
+ return resolvePath(process.cwd());
1552
+ }
1553
+
1554
+ return resolvePath(output);
1555
+ }
1556
+
1019
1557
  function normalizeButtonConfig(rawConfig) {
1020
1558
  if (rawConfig === undefined || rawConfig === null) {
1021
1559
  return null;
@@ -1307,6 +1845,7 @@ async function updateAppState(store, appName, nextState) {
1307
1845
 
1308
1846
  async function persistMapStore(store) {
1309
1847
  if (!store.persistMap) return;
1848
+ await mkdir(dirnamePath(store.path), { recursive: true });
1310
1849
  const serialized = JSON.stringify(store.map, null, 2) + '\n';
1311
1850
  await writeFile(store.path, serialized, 'utf8');
1312
1851
  }
@@ -1420,10 +1959,6 @@ function createActionHandlers(robot, runtime) {
1420
1959
  throw new Error(`missing_launcher:${appName}`);
1421
1960
  }
1422
1961
 
1423
- if (config.launchKey) {
1424
- robot.keyTap(config.launchKey);
1425
- }
1426
-
1427
1962
  if (appConfig.launcher.path) {
1428
1963
  await clickTarget(robot, appConfig.launcher, config);
1429
1964
  } else {
@@ -1499,6 +2034,58 @@ function createActionHandlers(robot, runtime) {
1499
2034
  return config.sequenceDoubleClick;
1500
2035
  }
1501
2036
 
2037
+ async function tapKey(key, modifiers) {
2038
+ if (modifiers && modifiers.length > 0) {
2039
+ robot.keyTap(key, modifiers);
2040
+ return;
2041
+ }
2042
+ robot.keyTap(key);
2043
+ }
2044
+
2045
+ function resolveKeyTapPayload(command) {
2046
+ const key = normalizeKeyTapToken(
2047
+ command.key !== undefined ? command.key : command.command
2048
+ );
2049
+
2050
+ if (!key) {
2051
+ throw new Error('key_tap_requires_key');
2052
+ }
2053
+
2054
+ const modifiers =
2055
+ command.modifiers !== undefined
2056
+ ? parseKeyModifiers(command.modifiers)
2057
+ : parseKeyModifiers(command.extra);
2058
+
2059
+ return {
2060
+ key,
2061
+ modifiers
2062
+ };
2063
+ }
2064
+
2065
+ function resolveTypeStringPayload(command) {
2066
+ const text =
2067
+ command.text !== undefined && command.text !== null
2068
+ ? String(command.text)
2069
+ : command.command !== undefined && command.command !== null
2070
+ ? String(command.command)
2071
+ : command.extra !== undefined && command.extra !== null
2072
+ ? String(command.extra)
2073
+ : '';
2074
+
2075
+ if (!text.trim()) {
2076
+ throw new Error('type_string_delayed_requires_text');
2077
+ }
2078
+
2079
+ const customDelay = toNonNegativeInteger(
2080
+ command.typeDelay !== undefined ? command.typeDelay : command.interval
2081
+ );
2082
+
2083
+ return {
2084
+ text,
2085
+ typeDelay: customDelay !== null ? customDelay : config.typeDelay
2086
+ };
2087
+ }
2088
+
1502
2089
  return {
1503
2090
  async skip_ads(command) {
1504
2091
  let point = config.skipAdsPoint;
@@ -1598,7 +2185,19 @@ function createActionHandlers(robot, runtime) {
1598
2185
  }
1599
2186
  },
1600
2187
 
2188
+ async key_tap(command) {
2189
+ const payload = resolveKeyTapPayload(command);
2190
+ await tapKey(payload.key, payload.modifiers);
2191
+ },
2192
+
2193
+ async type_string_delayed(command) {
2194
+ const payload = resolveTypeStringPayload(command);
2195
+ robot.typeStringDelayed(payload.text, payload.typeDelay);
2196
+ },
2197
+
1601
2198
  async open_app(command) {
2199
+ await tapKey('command');
2200
+
1602
2201
  const mapStore = await getMapStore();
1603
2202
  const appRef = resolveAppInMap(mapStore, command.command);
1604
2203
  if (!appRef.app) {
@@ -1626,7 +2225,12 @@ function createActionHandlers(robot, runtime) {
1626
2225
  if (appRef.app.searchIcon && appRef.app.searchIcon.position) {
1627
2226
  await clickTarget(robot, appRef.app.searchIcon, config);
1628
2227
  }
1629
- robot.typeStringDelayed(String(command.extra), config.typeDelay);
2228
+
2229
+ const customTypeDelay = toNonNegativeInteger(command.typeDelay);
2230
+ const typeDelay =
2231
+ customTypeDelay !== null ? customTypeDelay : config.typeDelay;
2232
+
2233
+ robot.typeStringDelayed(String(command.extra), typeDelay);
1630
2234
  }
1631
2235
  },
1632
2236
 
package/doc.md CHANGED
@@ -7,6 +7,9 @@ Exported functions:
7
7
  - `cmd`
8
8
  - `extractFirstCommand`
9
9
  - `cursor`
10
+ - `initMap`
11
+ - `initDoc`
12
+ - `maper`
10
13
 
11
14
  ## Installation
12
15
 
@@ -17,7 +20,7 @@ npm install command-cmd
17
20
  ## Import
18
21
 
19
22
  ```js
20
- import { cmd, extractFirstCommand, cursor } from 'command-cmd';
23
+ import { cmd, extractFirstCommand, cursor, initMap, initDoc, maper } from 'command-cmd';
21
24
  ```
22
25
 
23
26
  ## 1) `cmd(message)`
@@ -61,7 +64,23 @@ Returns only the first valid command.
61
64
  Output:
62
65
 
63
66
  ```js
64
- { type, command, extra, button, points, path, interval, clickDelay, click, doubleClick, seq } | null
67
+ {
68
+ type,
69
+ command,
70
+ extra,
71
+ button,
72
+ key,
73
+ modifiers,
74
+ text,
75
+ typeDelay,
76
+ points,
77
+ path,
78
+ interval,
79
+ clickDelay,
80
+ click,
81
+ doubleClick,
82
+ seq
83
+ } | null
65
84
  ```
66
85
 
67
86
  ## 3) `cursor(input, options?)`
@@ -79,6 +98,10 @@ Accepted object keys:
79
98
  - `type` or `tipo`
80
99
  - `command` or `comando`
81
100
  - `button` or `botao`
101
+ - `key` or `tecla` (for `key_tap`)
102
+ - `modifiers` or `modificadores` (example: `command+shift`)
103
+ - `text` or `texto` (for `type_string_delayed`)
104
+ - `typeDelay` or `typingDelay` (ms per character while typing)
82
105
  - `points` or `pontos` (coordinate sequence)
83
106
  - `path` or `caminho` or `trajeto` (alias of `points`)
84
107
  - `interval` or `intervalo` (ms between points)
@@ -88,6 +111,67 @@ Accepted object keys:
88
111
  - `seq` or `sequencia`
89
112
  - `extra`
90
113
 
114
+ ## 4) `initMap(options?)`
115
+
116
+ Creates (or loads/merges) `map.json` explicitly.
117
+
118
+ Example:
119
+
120
+ ```js
121
+ await initMap();
122
+ await initMap({ mapPath: './config/map.json' });
123
+ ```
124
+
125
+ Optional keys:
126
+
127
+ - `mapPath`
128
+ - `createMapIfMissing`
129
+ - `persistMap`
130
+ - `apps`
131
+ - `defaultClosePoint`
132
+ - `skipAdsPoint`
133
+ - `persistMerged` (writes merged defaults even when map already exists)
134
+
135
+ ## 5) `initDoc(options?)`
136
+
137
+ Creates documentation files from package templates.
138
+
139
+ By default it writes in current working directory:
140
+
141
+ - `doc.md`
142
+ - `docPTBR.md`
143
+ - `map.md`
144
+ - `mapPTBR.md`
145
+
146
+ Example:
147
+
148
+ ```js
149
+ await initDoc();
150
+ await initDoc({ outputDir: './docs', overwrite: true, includeMapDocs: false });
151
+ ```
152
+
153
+ Optional keys:
154
+
155
+ - `outputDir` (alias: `dir`, `path`)
156
+ - `overwrite`
157
+ - `includeMapDocs`
158
+ - `files` (e.g. `['doc', 'docPTBR', 'map', 'mapPTBR']`)
159
+
160
+ ## 6) `maper(enabled)`
161
+
162
+ Turns terminal mouse position mapping on/off.
163
+
164
+ - pass `true` to start mapping
165
+ - pass `false` to stop mapping
166
+ - while active, it prints current `x/y` only when mouse position changes
167
+
168
+ Example:
169
+
170
+ ```js
171
+ await maper(true); // starts mapper
172
+ await maper(false); // stops mapper
173
+ ```
174
+
91
175
  ### App and button names (important)
92
176
 
93
177
  You can name apps and buttons however you want.
@@ -131,10 +215,18 @@ For app commands (`open_app`, `close_app`), the library uses `map.json`:
131
215
  3. Executes mapped button click.
132
216
  4. If that button has `setState`, it updates app state in `map.json`.
133
217
 
218
+ Note: `open_app/abrir_app` always triggers `robot.keyTap("command")` before app flow.
219
+
134
220
  ### Automatic `map.json` creation
135
221
 
136
222
  If `map.json` does not exist, the library creates it automatically in the project root (cwd) by default.
137
223
 
224
+ You can also create it explicitly before any automation:
225
+
226
+ ```js
227
+ await initMap();
228
+ ```
229
+
138
230
  The generated file includes:
139
231
 
140
232
  - `state` for each app;
@@ -162,6 +254,28 @@ AI output example:
162
254
  - `scroll` / `rolar`
163
255
  - `move_sequence` / `mover_sequencia`
164
256
  - `skip_ads` / `pular_ads`
257
+ - `key_tap` / `tecla`
258
+ - `type_string_delayed` / `digitar`
259
+
260
+ ## Keyboard and typing
261
+
262
+ Single key:
263
+
264
+ ```txt
265
+ [key_tap, key: enter]
266
+ ```
267
+
268
+ With modifier:
269
+
270
+ ```txt
271
+ [key_tap, key: v, modifiers: command]
272
+ ```
273
+
274
+ Slow typing:
275
+
276
+ ```txt
277
+ [type_string_delayed, text: Typing slowly..., typeDelay: 100]
278
+ ```
165
279
 
166
280
  ## Sequential movement (new)
167
281
 
@@ -236,7 +350,7 @@ await cursor({
236
350
  mapPath: 'map.json',
237
351
  createMapIfMissing: true,
238
352
  persistMap: true,
239
- launchKey: 'command', // set false to disable launcher key tap
353
+ launchKey: 'command', // used by close_app fallback shortcut (command+w)
240
354
  moveSteps: 50,
241
355
  moveDelay: 8,
242
356
  scrollSteps: 10,
@@ -267,7 +381,7 @@ They explain:
267
381
  ```txt
268
382
  You are a technical assistant.
269
383
  When you suggest executable actions, output only blocks:
270
- [type: <type>, command: <app_or_value>, points: <optional_points>, button: <optional_button>, interval: <optional_ms>, clickDelay: <optional_ms_before_click>, click: <optional_mode>, doubleClick: <optional_boolean>, extra: <optional_text>, seq: <optional_integer>]
384
+ [type: <type>, command: <app_or_value>, key: <optional_key>, modifiers: <optional_modifiers>, text: <optional_text>, typeDelay: <typing_ms>, points: <optional_points>, button: <optional_button>, interval: <optional_ms>, clickDelay: <optional_ms_before_click>, click: <optional_mode>, doubleClick: <optional_boolean>, extra: <optional_text>, seq: <optional_integer>]
271
385
 
272
386
  Rules:
273
387
  1. Always use square brackets.
package/docPTBR.md CHANGED
@@ -7,6 +7,9 @@ Funcoes exportadas:
7
7
  - `cmd`
8
8
  - `extractFirstCommand`
9
9
  - `cursor`
10
+ - `initMap`
11
+ - `initDoc`
12
+ - `maper`
10
13
 
11
14
  ## Instalacao
12
15
 
@@ -20,7 +23,10 @@ npm install command-cmd
20
23
  import {
21
24
  cmd as extrairComandos,
22
25
  extractFirstCommand as extrairPrimeiroComando,
23
- cursor as executarCursor
26
+ cursor as executarCursor,
27
+ initMap as iniciarMap,
28
+ initDoc as iniciarDoc,
29
+ maper
24
30
  } from 'command-cmd';
25
31
  ```
26
32
 
@@ -65,7 +71,23 @@ Retorna apenas o primeiro comando valido.
65
71
  Saida:
66
72
 
67
73
  ```js
68
- { type, command, extra, button, points, path, interval, clickDelay, click, doubleClick, seq } | null
74
+ {
75
+ type,
76
+ command,
77
+ extra,
78
+ button,
79
+ key,
80
+ modifiers,
81
+ text,
82
+ typeDelay,
83
+ points,
84
+ path,
85
+ interval,
86
+ clickDelay,
87
+ click,
88
+ doubleClick,
89
+ seq
90
+ } | null
69
91
  ```
70
92
 
71
93
  ## 3) `cursor(input, options?)`
@@ -83,6 +105,10 @@ Campos aceitos no objeto:
83
105
  - `type` ou `tipo`
84
106
  - `command` ou `comando`
85
107
  - `button` ou `botao`
108
+ - `key` ou `tecla` (para `key_tap`)
109
+ - `modifiers` ou `modificadores` (ex.: `command+shift`)
110
+ - `text` ou `texto` (para `type_string_delayed`)
111
+ - `typeDelay` ou `typingDelay` (ms por caractere na digitacao)
86
112
  - `points` ou `pontos` (sequencia de coordenadas)
87
113
  - `path` ou `caminho` ou `trajeto` (alias de `points`)
88
114
  - `interval` ou `intervalo` (ms entre pontos)
@@ -92,6 +118,67 @@ Campos aceitos no objeto:
92
118
  - `seq` ou `sequencia`
93
119
  - `extra`
94
120
 
121
+ ## 4) `initMap(options?)`
122
+
123
+ Cria (ou carrega/mescla) o `map.json` de forma explicita.
124
+
125
+ Exemplo:
126
+
127
+ ```js
128
+ await iniciarMap();
129
+ await iniciarMap({ mapPath: './config/map.json' });
130
+ ```
131
+
132
+ Campos opcionais:
133
+
134
+ - `mapPath`
135
+ - `createMapIfMissing`
136
+ - `persistMap`
137
+ - `apps`
138
+ - `defaultClosePoint`
139
+ - `skipAdsPoint`
140
+ - `persistMerged` (salva defaults mesclados mesmo se o map ja existir)
141
+
142
+ ## 5) `initDoc(options?)`
143
+
144
+ Cria os arquivos de documentacao usando os templates do pacote.
145
+
146
+ Por padrao, escreve no diretório atual:
147
+
148
+ - `doc.md`
149
+ - `docPTBR.md`
150
+ - `map.md`
151
+ - `mapPTBR.md`
152
+
153
+ Exemplo:
154
+
155
+ ```js
156
+ await iniciarDoc();
157
+ await iniciarDoc({ outputDir: './docs', overwrite: true, includeMapDocs: false });
158
+ ```
159
+
160
+ Campos opcionais:
161
+
162
+ - `outputDir` (alias: `dir`, `path`)
163
+ - `overwrite`
164
+ - `includeMapDocs`
165
+ - `files` (ex.: `['doc', 'docPTBR', 'map', 'mapPTBR']`)
166
+
167
+ ## 6) `maper(enabled)`
168
+
169
+ Liga/desliga o mapeador de posicao do mouse no terminal.
170
+
171
+ - passe `true` para ativar
172
+ - passe `false` para desativar
173
+ - enquanto ativo, ele mostra `x/y` apenas quando a posicao muda
174
+
175
+ Exemplo:
176
+
177
+ ```js
178
+ await maper(true); // inicia o mapeador
179
+ await maper(false); // encerra o mapeador
180
+ ```
181
+
95
182
  ### Nome dos apps e botoes (importante)
96
183
 
97
184
  Voce pode chamar apps e botoes como quiser.
@@ -135,10 +222,18 @@ Para comandos de app (`abrir_app`, `close_app`), a lib usa `map.json`:
135
222
  3. Executa o clique no botao mapeado.
136
223
  4. Se o botao tiver `setState`, atualiza o estado no `map.json`.
137
224
 
225
+ Obs.: `abrir_app/open_app` sempre dispara `robot.keyTap("command")` antes do fluxo do app.
226
+
138
227
  ### Criacao automatica do `map.json`
139
228
 
140
229
  Se o arquivo `map.json` nao existir, a lib cria automaticamente na raiz do projeto (cwd), por padrao.
141
230
 
231
+ Tambem da para criar explicitamente antes das automacoes:
232
+
233
+ ```js
234
+ await iniciarMap();
235
+ ```
236
+
142
237
  Esse arquivo inicial ja vem com:
143
238
 
144
239
  - `state` em cada app;
@@ -166,6 +261,28 @@ A IA pode converter para:
166
261
  - `scroll` / `rolar`
167
262
  - `mover_sequencia` / `move_sequence`
168
263
  - `skip_ads` / `pular_ads`
264
+ - `key_tap` / `tecla`
265
+ - `type_string_delayed` / `digitar`
266
+
267
+ ## Teclado e digitacao
268
+
269
+ Atalho simples:
270
+
271
+ ```txt
272
+ [key_tap, key: enter]
273
+ ```
274
+
275
+ Atalho com modificador:
276
+
277
+ ```txt
278
+ [key_tap, key: v, modifiers: command]
279
+ ```
280
+
281
+ Digitando devagar:
282
+
283
+ ```txt
284
+ [type_string_delayed, text: Digitando devagar..., typeDelay: 100]
285
+ ```
169
286
 
170
287
  ## Movimento sequencial (novo)
171
288
 
@@ -240,7 +357,7 @@ await executarCursor({
240
357
  mapPath: 'map.json', // caminho do mapeador
241
358
  createMapIfMissing: true, // padrao: cria map automaticamente se faltar
242
359
  persistMap: true, // salva estado no arquivo
243
- launchKey: 'command', // use false para nao disparar tecla de launcher
360
+ launchKey: 'command', // usado no fallback de close_app (atalho command+w)
244
361
  moveSteps: 50,
245
362
  moveDelay: 8,
246
363
  scrollSteps: 10,
@@ -272,7 +389,7 @@ Esses arquivos explicam:
272
389
  ```txt
273
390
  Voce e um assistente tecnico.
274
391
  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>]
392
+ [tipo: <tipo>, comando: <app_ou_valor>, key: <tecla_opcional>, modifiers: <mods_opcional>, text: <texto_opcional>, typeDelay: <ms_digitacao>, 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
393
 
277
394
  Regras:
278
395
  1. Use sempre colchetes.
package/map.md CHANGED
@@ -14,6 +14,12 @@ Create `map.json` in your project root (where Node runs).
14
14
 
15
15
  By default, the library auto-creates this file if it does not exist.
16
16
 
17
+ You can also create it explicitly:
18
+
19
+ ```js
20
+ await initMap();
21
+ ```
22
+
17
23
  If you need another path:
18
24
 
19
25
  ```js
package/mapPTBR.md CHANGED
@@ -14,6 +14,12 @@ Crie `map.json` na raiz do seu projeto (mesma pasta onde voce roda o Node).
14
14
 
15
15
  Por padrao, a lib cria esse arquivo automaticamente se ele nao existir.
16
16
 
17
+ Tambem da para criar explicitamente:
18
+
19
+ ```js
20
+ await initMap();
21
+ ```
22
+
17
23
  Se quiser outro caminho, passe em `cursor`:
18
24
 
19
25
  ```js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "command-cmd",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "main": "cmd.js",
5
5
  "type": "module",
6
6
  "scripts": {