@skip-ai/scanner 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +81 -0
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +21 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/detector.d.ts +14 -0
  7. package/dist/detector.d.ts.map +1 -0
  8. package/dist/detector.js +72 -0
  9. package/dist/detector.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +115 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/reporter.d.ts +55 -0
  15. package/dist/reporter.d.ts.map +1 -0
  16. package/dist/reporter.js +38 -0
  17. package/dist/reporter.js.map +1 -0
  18. package/dist/scanner/api-calls.d.ts +11 -0
  19. package/dist/scanner/api-calls.d.ts.map +1 -0
  20. package/dist/scanner/api-calls.js +45 -0
  21. package/dist/scanner/api-calls.js.map +1 -0
  22. package/dist/scanner/briefing.d.ts +7 -0
  23. package/dist/scanner/briefing.d.ts.map +1 -0
  24. package/dist/scanner/briefing.js +50 -0
  25. package/dist/scanner/briefing.js.map +1 -0
  26. package/dist/scanner/components.d.ts +24 -0
  27. package/dist/scanner/components.d.ts.map +1 -0
  28. package/dist/scanner/components.js +71 -0
  29. package/dist/scanner/components.js.map +1 -0
  30. package/dist/scanner/ignore.d.ts +13 -0
  31. package/dist/scanner/ignore.d.ts.map +1 -0
  32. package/dist/scanner/ignore.js +50 -0
  33. package/dist/scanner/ignore.js.map +1 -0
  34. package/dist/scanner/navigation.d.ts +50 -0
  35. package/dist/scanner/navigation.d.ts.map +1 -0
  36. package/dist/scanner/navigation.js +478 -0
  37. package/dist/scanner/navigation.js.map +1 -0
  38. package/dist/scanner/routes.d.ts +12 -0
  39. package/dist/scanner/routes.d.ts.map +1 -0
  40. package/dist/scanner/routes.js +74 -0
  41. package/dist/scanner/routes.js.map +1 -0
  42. package/dist/scanner/secrets.d.ts +18 -0
  43. package/dist/scanner/secrets.d.ts.map +1 -0
  44. package/dist/scanner/secrets.js +36 -0
  45. package/dist/scanner/secrets.js.map +1 -0
  46. package/dist/scanner/widget-snippet.d.ts +14 -0
  47. package/dist/scanner/widget-snippet.d.ts.map +1 -0
  48. package/dist/scanner/widget-snippet.js +114 -0
  49. package/dist/scanner/widget-snippet.js.map +1 -0
  50. package/dist/ui.d.ts +15 -0
  51. package/dist/ui.d.ts.map +1 -0
  52. package/dist/ui.js +32 -0
  53. package/dist/ui.js.map +1 -0
  54. package/dist/uploader.d.ts +27 -0
  55. package/dist/uploader.d.ts.map +1 -0
  56. package/dist/uploader.js +73 -0
  57. package/dist/uploader.js.map +1 -0
  58. package/package.json +51 -0
@@ -0,0 +1,36 @@
1
+ // Detecta e mascara secrets ANTES de enviar o payload para a nuvem.
2
+ const SECRET_PATTERNS = [
3
+ /sk-[a-zA-Z0-9]{20,}/g, // OpenAI / Anthropic API Key
4
+ /AKIA[0-9A-Z]{16}/g, // AWS Access Key
5
+ /[a-zA-Z0-9_-]{20,}:[a-zA-Z0-9_-]{20,}/g, // Generic key:secret
6
+ /(password|senha|secret|api_key|apikey|token)\s*[:=]\s*['"][^'"]{8,}['"]/gi, // Env values
7
+ /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g, // JWT tokens
8
+ /ghp_[a-zA-Z0-9]{30,}/g, // GitHub PAT
9
+ /xox[baprs]-[a-zA-Z0-9-]{10,}/g, // Slack token
10
+ ];
11
+ export const MASKED_TOKEN = '[MASKED_SECRET]';
12
+ /**
13
+ * Mascara ocorrências de secrets em um conteúdo de texto e retorna
14
+ * o conteúdo mascarado + a contagem total de substituições.
15
+ */
16
+ export function countAndMaskSecrets(content) {
17
+ let masked = content;
18
+ let count = 0;
19
+ for (const pattern of SECRET_PATTERNS) {
20
+ masked = masked.replace(pattern, () => {
21
+ count++;
22
+ return MASKED_TOKEN;
23
+ });
24
+ }
25
+ return { masked, count };
26
+ }
27
+ /**
28
+ * Recebe um objeto arbitrário, serializa, mascara secrets e retorna
29
+ * o objeto novamente + a contagem de secrets encontrados.
30
+ */
31
+ export function maskObject(obj) {
32
+ const serialized = JSON.stringify(obj);
33
+ const { masked, count } = countAndMaskSecrets(serialized);
34
+ return { result: JSON.parse(masked), count };
35
+ }
36
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/scanner/secrets.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,MAAM,eAAe,GAAG;IACtB,sBAAsB,EAAE,6BAA6B;IACrD,mBAAmB,EAAE,iBAAiB;IACtC,wCAAwC,EAAE,qBAAqB;IAC/D,2EAA2E,EAAE,aAAa;IAC1F,gEAAgE,EAAE,aAAa;IAC/E,uBAAuB,EAAE,aAAa;IACtC,+BAA+B,EAAE,cAAc;CAChD,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,IAAI,MAAM,GAAG,OAAO,CAAC;IACrB,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACtC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE;YACpC,KAAK,EAAE,CAAC;YACR,OAAO,YAAY,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAI,GAAM;IAClC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC1D,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAM,EAAE,KAAK,EAAE,CAAC;AACpD,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Gera o snippet JS que o widget carrega em runtime para injetar
3
+ * atributos `data-skip-anchor` automaticamente nos elementos mapeados,
4
+ * sem exigir que o desenvolvedor do site cliente marque nada.
5
+ *
6
+ * O snippet é uma string JS pronta para ser embutida no widget.
7
+ * Em runtime ele:
8
+ * 1. Lê o navigationMap (passado por parâmetro ou em window.__SKIP_MAP__)
9
+ * 2. Procura no DOM elementos que casem com cada ação (seletor/id/aria-label/texto/name)
10
+ * 3. Marca data-skip-anchor="<id>" nesses elementos
11
+ * 4. Observa mudanças (MutationObserver) para SPAs que trocam de tela sem reload
12
+ */
13
+ export declare function buildWidgetSnippet(): string;
14
+ //# sourceMappingURL=widget-snippet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-snippet.d.ts","sourceRoot":"","sources":["../../src/scanner/widget-snippet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAoG3C"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Gera o snippet JS que o widget carrega em runtime para injetar
3
+ * atributos `data-skip-anchor` automaticamente nos elementos mapeados,
4
+ * sem exigir que o desenvolvedor do site cliente marque nada.
5
+ *
6
+ * O snippet é uma string JS pronta para ser embutida no widget.
7
+ * Em runtime ele:
8
+ * 1. Lê o navigationMap (passado por parâmetro ou em window.__SKIP_MAP__)
9
+ * 2. Procura no DOM elementos que casem com cada ação (seletor/id/aria-label/texto/name)
10
+ * 3. Marca data-skip-anchor="<id>" nesses elementos
11
+ * 4. Observa mudanças (MutationObserver) para SPAs que trocam de tela sem reload
12
+ */
13
+ export function buildWidgetSnippet() {
14
+ return `/* Skip AI — auto-anchor snippet (gerado pelo scanner) */
15
+ (function () {
16
+ if (window.__SKIP_ANCHOR_INIT__) return;
17
+ window.__SKIP_ANCHOR_INIT__ = true;
18
+
19
+ function getText(el) {
20
+ return (el.innerText || el.textContent || '').trim().replace(/\\s+/g, ' ').toLowerCase();
21
+ }
22
+ function getAttr(el, name) {
23
+ return (el.getAttribute(name) || '').trim();
24
+ }
25
+
26
+ // Tenta casar uma ação com um elemento do DOM.
27
+ function matchesAction(el, action) {
28
+ // 1. id estável
29
+ if (action.anchorId && el.id === action.anchorId) return true;
30
+ // 2. data-skip-anchor explícito
31
+ if (action.anchorId && getAttr(el, 'data-skip-anchor') === action.anchorId) return true;
32
+ // 3. input por name
33
+ if (action.kind === 'fill' && action.inputName && getAttr(el, 'name') === action.inputName) return true;
34
+ // 4. aria-label
35
+ var label = (action.label || '').toLowerCase();
36
+ if (label && getAttr(el, 'aria-label').toLowerCase() === label) return true;
37
+ // 5. texto visível (botões/links)
38
+ if (label && getText(el) === label) return true;
39
+ // 6. tipo de input (email/password) como último recurso
40
+ if (action.kind === 'fill' && action.inputType && getAttr(el, 'type') === action.inputType) return true;
41
+ return false;
42
+ }
43
+
44
+ function tagForAction(action) {
45
+ if (action.kind === 'fill') return ['input', 'textarea', 'select'];
46
+ if (action.kind === 'navigation') return ['a', 'button', '[role=link]'];
47
+ return ['button', 'a', '[role=button]'];
48
+ }
49
+
50
+ // Aplica anchors para uma lista de ações (da tela atual).
51
+ function applyAnchors(actions) {
52
+ if (!actions || !actions.length) return;
53
+ actions.forEach(function (action) {
54
+ var tags = tagForAction(action);
55
+ var candidates = document.querySelectorAll(tags.join(','));
56
+ for (var i = 0; i < candidates.length; i++) {
57
+ var el = candidates[i];
58
+ if (el.hasAttribute('data-skip-anchor')) continue;
59
+ if (matchesAction(el, action)) {
60
+ el.setAttribute('data-skip-anchor', action.id);
61
+ // Opcionalmente guarda o seletor para o widget
62
+ el.setAttribute('data-skip-selector', action.selector);
63
+ }
64
+ }
65
+ });
66
+ }
67
+
68
+ // Resolve a rota atual em paths absolutos.
69
+ function currentPath() {
70
+ return window.location.pathname;
71
+ }
72
+
73
+ // Encontra as ações da tela atual no mapa.
74
+ function actionsForCurrentScreen(map) {
75
+ if (!map || !map.screens) return [];
76
+ var path = currentPath();
77
+ var screen = map.screens.find(function (s) { return s.route === path; });
78
+ if (!screen) {
79
+ // fallback: tela raiz
80
+ screen = map.screens.find(function (s) { return s.route === '/'; });
81
+ }
82
+ if (!screen) return [];
83
+ // Ações da tela + navegação global convertida em pseudo-ações
84
+ var acts = (screen.actions || []).slice();
85
+ (screen.globalActions || []).forEach(function (g) {
86
+ acts.push({ id: 'global-' + g.label, label: g.label, kind: 'navigation', targetRoute: g.targetRoute, selector: '', anchorId: null });
87
+ });
88
+ return acts;
89
+ }
90
+
91
+ function run() {
92
+ var map = window.__SKIP_MAP__ || null;
93
+ if (!map) return;
94
+ applyAnchors(actionsForCurrentScreen(map));
95
+ }
96
+
97
+ // Observa mudanças no DOM (navegação SPA) e re-aplica.
98
+ var observer = new MutationObserver(function () { run(); });
99
+ observer.observe(document.documentElement, { childList: true, subtree: true });
100
+
101
+ // Re-aplica em mudanças de rota (popstate / pushState).
102
+ window.addEventListener('popstate', run);
103
+ var origPush = history.pushState;
104
+ var origReplace = history.replaceState;
105
+ history.pushState = function () { origPush.apply(this, arguments); setTimeout(run, 50); };
106
+ history.replaceState = function () { origReplace.apply(this, arguments); setTimeout(run, 50); };
107
+
108
+ // Dispara na carga e periodicamente por alguns segundos (caso o mapa chegue depois).
109
+ run();
110
+ var n = 0;
111
+ var iv = setInterval(function () { run(); if (++n >= 12) clearInterval(iv); }, 500);
112
+ })();`;
113
+ }
114
+ //# sourceMappingURL=widget-snippet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-snippet.js","sourceRoot":"","sources":["../../src/scanner/widget-snippet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAkGH,CAAC;AACP,CAAC"}
package/dist/ui.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { type Ora } from 'ora';
2
+ /** Cor de marca da Skip AI. */
3
+ export declare const BRAND_HEX = "#5922f2";
4
+ export declare function banner(version: string): string;
5
+ export declare function success(msg: string): string;
6
+ export declare function fail(msg: string): string;
7
+ export declare function spinner(text: string): Ora;
8
+ /** Imprime o resumo final do scan. */
9
+ export declare function printSummary(opts: {
10
+ scanId: string;
11
+ entitiesCount?: number;
12
+ baseUrl: string;
13
+ status: string;
14
+ }): void;
15
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AACA,OAAY,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAEpC,+BAA+B;AAC/B,eAAO,MAAM,SAAS,YAAY,CAAC;AAEnC,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAEzC;AAED,sCAAsC;AACtC,wBAAgB,YAAY,CAAC,IAAI,EAAE;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CAaP"}
package/dist/ui.js ADDED
@@ -0,0 +1,32 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ /** Cor de marca da Skip AI. */
4
+ export const BRAND_HEX = '#5922f2';
5
+ export function banner(version) {
6
+ return chalk.bold.hex(BRAND_HEX)(`\n⚡ Skip AI Scanner v${version}\n`);
7
+ }
8
+ export function success(msg) {
9
+ return chalk.green(`✔ ${msg}`);
10
+ }
11
+ export function fail(msg) {
12
+ return chalk.red(`✖ ${msg}`);
13
+ }
14
+ export function spinner(text) {
15
+ return ora(text).start();
16
+ }
17
+ /** Imprime o resumo final do scan. */
18
+ export function printSummary(opts) {
19
+ const { scanId, entitiesCount, baseUrl, status } = opts;
20
+ if (status === 'COMPLETED') {
21
+ console.log(chalk.bold.green('\n✅ Scan concluído com sucesso!\n'));
22
+ console.log(` Scan ID: ${chalk.yellow(scanId)}`);
23
+ console.log(` Entidades mapeadas: ${chalk.bold(entitiesCount ?? 0)}`);
24
+ console.log(` Dashboard: ${chalk.cyan(`${baseUrl}/dashboard`)}\n`);
25
+ }
26
+ else {
27
+ console.log(chalk.bold.red('\n✖ Scan não foi concluído.\n'));
28
+ console.log(` Scan ID: ${chalk.yellow(scanId)}`);
29
+ console.log(` Status: ${chalk.red(status)}\n`);
30
+ }
31
+ }
32
+ //# sourceMappingURL=ui.js.map
package/dist/ui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAiB,MAAM,KAAK,CAAC;AAEpC,+BAA+B;AAC/B,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC;AAEnC,MAAM,UAAU,MAAM,CAAC,OAAe;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,wBAAwB,OAAO,IAAI,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;AAC3B,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,YAAY,CAAC,IAK5B;IACC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAExD,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,yBAAyB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,yBAAyB,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ export type ScanStatus = 'PROCESSING' | 'ENRICHING' | 'COMPLETED' | 'FAILED';
2
+ export interface ScanStatusResponse {
3
+ scanId: string;
4
+ status: ScanStatus;
5
+ entitiesCount: number;
6
+ errorMessage: string | null;
7
+ }
8
+ export interface UploadResponse {
9
+ scanId: string;
10
+ }
11
+ /** Limite de tamanho do payload (5 MB) imposto pela API. */
12
+ export declare const MAX_PAYLOAD_BYTES: number;
13
+ /**
14
+ * Envia o relatório para o endpoint /api/scanner da plataforma Skip.
15
+ * Retorna o scanId criado.
16
+ */
17
+ export declare function uploadReport(report: object, token: string, baseUrl: string): Promise<string>;
18
+ /**
19
+ * Faz polling do status do scan até `COMPLETED` ou `FAILED`,
20
+ * exibindo um spinner no terminal.
21
+ */
22
+ export declare function pollStatus(scanId: string, token: string, baseUrl: string, opts?: {
23
+ maxAttempts?: number;
24
+ intervalMs?: number;
25
+ spinner?: boolean;
26
+ }): Promise<ScanStatusResponse>;
27
+ //# sourceMappingURL=uploader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../src/uploader.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE7E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,QAAkB,CAAC;AAEjD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1E,OAAO,CAAC,kBAAkB,CAAC,CAsC7B"}
@@ -0,0 +1,73 @@
1
+ import ora from 'ora';
2
+ /** Limite de tamanho do payload (5 MB) imposto pela API. */
3
+ export const MAX_PAYLOAD_BYTES = 5 * 1024 * 1024;
4
+ /**
5
+ * Envia o relatório para o endpoint /api/scanner da plataforma Skip.
6
+ * Retorna o scanId criado.
7
+ */
8
+ export async function uploadReport(report, token, baseUrl) {
9
+ const body = JSON.stringify({ action: 'receiveReport', report });
10
+ if (Buffer.byteLength(body) > MAX_PAYLOAD_BYTES) {
11
+ throw new Error('Payload muito grande (>5MB). Use --ignore para excluir mais arquivos.');
12
+ }
13
+ const res = await fetch(`${baseUrl}/api/scanner`, {
14
+ method: 'POST',
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ Authorization: `Bearer ${token}`,
18
+ },
19
+ body,
20
+ });
21
+ if (res.status === 401) {
22
+ throw new Error('Token inválido. Verifique seu token no Dashboard.');
23
+ }
24
+ if (res.status === 413) {
25
+ throw new Error('Payload muito grande (>5MB). Use --ignore para excluir mais arquivos.');
26
+ }
27
+ if (!res.ok) {
28
+ const text = await res.text().catch(() => '');
29
+ throw new Error(`Erro ao enviar scan: HTTP ${res.status}${text ? ` — ${text}` : ''}`);
30
+ }
31
+ const data = (await res.json());
32
+ return data.scanId;
33
+ }
34
+ /**
35
+ * Faz polling do status do scan até `COMPLETED` ou `FAILED`,
36
+ * exibindo um spinner no terminal.
37
+ */
38
+ export async function pollStatus(scanId, token, baseUrl, opts = {}) {
39
+ const MAX_ATTEMPTS = opts.maxAttempts ?? 30; // 30 × 5s = 2.5 min
40
+ const INTERVAL_MS = opts.intervalMs ?? 5000;
41
+ const useSpinner = opts.spinner ?? true;
42
+ const spinner = useSpinner ? ora('Enriquecendo com IA...').start() : null;
43
+ let attempt = 0;
44
+ while (attempt < MAX_ATTEMPTS) {
45
+ await new Promise((r) => setTimeout(r, INTERVAL_MS));
46
+ attempt++;
47
+ let res;
48
+ try {
49
+ res = await fetch(`${baseUrl}/api/scanner/status/${scanId}`, {
50
+ headers: { Authorization: `Bearer ${token}` },
51
+ });
52
+ }
53
+ catch {
54
+ continue;
55
+ }
56
+ if (!res.ok)
57
+ continue;
58
+ const status = (await res.json());
59
+ if (status.status === 'COMPLETED') {
60
+ spinner?.succeed(`Enriquecimento concluído! ${status.entitiesCount} entidades mapeadas.`);
61
+ return status;
62
+ }
63
+ if (status.status === 'FAILED') {
64
+ spinner?.fail(`Falha no enriquecimento: ${status.errorMessage}`);
65
+ return status;
66
+ }
67
+ if (spinner)
68
+ spinner.text = `Status: ${status.status} (tentativa ${attempt}/${MAX_ATTEMPTS})...`;
69
+ }
70
+ spinner?.fail('Timeout: o scan demorou mais de 2.5 minutos.');
71
+ throw new Error('Timeout no polling de status.');
72
+ }
73
+ //# sourceMappingURL=uploader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploader.js","sourceRoot":"","sources":["../src/uploader.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@skip-ai/scanner",
3
+ "version": "0.2.0",
4
+ "description": "Scanner CLI para a plataforma Skip AI Accessibility Layer",
5
+ "type": "module",
6
+ "bin": {
7
+ "scanner": "dist/index.js",
8
+ "skip-scanner": "dist/index.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "exports": {
12
+ ".": "./dist/index.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsx src/index.ts",
21
+ "start": "node dist/index.js",
22
+ "test": "vitest run",
23
+ "clean": "node -e \"import('fs').then(fs=>fs.rmSync('dist',{recursive:true,force:true}))\"",
24
+ "prepack": "npm run clean && npm run build",
25
+ "prepublishOnly": "npm run clean && npm run build && npm test"
26
+ },
27
+ "dependencies": {
28
+ "chalk": "^5.3.0",
29
+ "commander": "^12.1.0",
30
+ "glob": "^11.0.0",
31
+ "ora": "^8.1.1"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.14.0",
35
+ "tsx": "^4.16.0",
36
+ "typescript": "^5.4.0",
37
+ "vitest": "^1.6.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "keywords": [
43
+ "skip",
44
+ "skip-ai",
45
+ "scanner",
46
+ "accessibility",
47
+ "cli",
48
+ "nextjs"
49
+ ],
50
+ "license": "MIT"
51
+ }