hobots 4.5.0 → 5.0.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.
- package/README.md +14 -11
- package/dist/cli.cjs +250 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/index.cjs +544 -222
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -250
- package/dist/index.d.ts +75 -250
- package/dist/index.js +544 -222
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +5 -2
- package/dist/protocol.d.ts +5 -2
- package/docs/README.md +2 -2
- package/docs/api-reference.md +21 -7
- package/docs/concepts.md +11 -11
- package/docs/getting-started.md +1 -1
- package/docs/protocol.md +5 -5
- package/docs/runs.md +97 -23
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -58,9 +58,7 @@ hobots.start(async () => {
|
|
|
58
58
|
while (true) {
|
|
59
59
|
await hobots.sleep(60_000); // cancelável
|
|
60
60
|
for (const arquivo of await novosArquivos()) {
|
|
61
|
-
|
|
62
|
-
await processar(arquivo);
|
|
63
|
-
step.succeeded();
|
|
61
|
+
await hobots.step(`arquivo ${arquivo}`, () => processar(arquivo)); // etapa no waterfall, com 1 nova tentativa
|
|
64
62
|
hobots.items.succeeded({ arquivo });
|
|
65
63
|
}
|
|
66
64
|
hobots.log.info('ciclo ok');
|
|
@@ -69,7 +67,7 @@ hobots.start(async () => {
|
|
|
69
67
|
});
|
|
70
68
|
```
|
|
71
69
|
|
|
72
|
-
O objeto **`hobots`** é global: `hobots.log`, `hobots.items`, `hobots.progress`, `hobots.
|
|
70
|
+
O objeto **`hobots`** é global: `hobots.log`, `hobots.items`, `hobots.progress`, `hobots.step`… resolvem a execução **atual** de qualquer módulo do programa — sem passar contexto de mão em mão, mesmo com várias execuções em paralelo. Depois do `register()`/`start()` ele também existe como `globalThis.hobots`.
|
|
73
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
@@ -82,6 +80,8 @@ O objeto **`hobots`** é global: `hobots.log`, `hobots.items`, `hobots.progress`
|
|
|
82
80
|
| `HOBOTS_RUN_ID` | `runId` | Definida por quem lança o processo (o Studio): o `register()` não faz poll — inicia essa run, executa o handler uma vez e sai. O código do bot não muda. |
|
|
83
81
|
| `HOBOTS_ENVIRONMENT` | `environment` | Ambiente lógico do robô (`development` \| `testing` \| `staging` \| `production`). Sem ela, `production`. |
|
|
84
82
|
| `HOBOTS_API_URL` | — | Só em desenvolvimento: aponta o SDK para outro host (`http://localhost:3001`). Em produção é sempre `https://api2.hobots.app`. |
|
|
83
|
+
| `HOBOTS_LOCAL_RUN` | — | `1` liga o **modo local** (teste sem a Central): sem secret, sem rede; o handler roda uma vez com os params de `HOBOTS_RUN_PARAMS` e o processo encerra com o exit code do desfecho. `npx hobots run bot.js …` define as duas por você. Ver [Execuções → Modo local](./docs/runs.md#modo-local-teste-sem-a-central). |
|
|
84
|
+
| `HOBOTS_RUN_PARAMS` | — | Modo local: objeto JSON dos params do handler (ausente = `{}`). |
|
|
85
85
|
|
|
86
86
|
A opção passada em `register()`/`start()` tem precedência sobre a env — útil para copiar e colar o snippet do app.
|
|
87
87
|
|
|
@@ -112,11 +112,11 @@ A opção passada em `register()`/`start()` tem precedência sobre a env — út
|
|
|
112
112
|
| --- | --- |
|
|
113
113
|
| `hobots.log.debug/info/warn/error/success(msg, ...rest)` | Log da execução, enviado incrementalmente ao app (e ecoado no terminal como `[hobots] INFO …`). Aceita vários argumentos como `console.log` — `hobots.log.info('nota', numero, 'ok')` — cada um vira texto, unidos por espaço. Um `hobots.image(...)` como **último** argumento pendura um print na linha. **Nunca lança** — pode ir em `finally`. |
|
|
114
114
|
| `hobots.items.succeeded(payload?, { id, image }?)` · `.failed(msg, payload?, …)` · `.occurrence(msg, payload?, …)` · `.add({...})` | **A entrega do robô**: cada item processado, com status e atributos (viram colunas no app — declare-as com a opção `items: { columns: [...] }` do `register`/`start`). `image` pendura um print no item. Bufferizado, em lotes. |
|
|
115
|
-
| `hobots.items.each(lista, async (el, item) => …, { id, payload }?)` | **Loop com o contrato garantido**: cada elemento vira
|
|
115
|
+
| `hobots.items.each(lista, async (el, item) => …, { id, payload }?)` | **Loop com o contrato garantido**: cada elemento vira etapa + item; exceção no handler = item `failed` com a mensagem e o loop **segue** até o fim; progresso automático; `hobots.flush()` ao fim de cada ciclo (`flush: false` desliga). Só cancelamento/timeout interrompem. |
|
|
116
116
|
| `hobots.progress.total(n)` · `.advance(k = 1)` · `.set(atual, total?)` · `.current` · `.snapshot` | Barra, percentual e tempo restante na execução. Sem `total()` nada é enviado. |
|
|
117
|
-
| `hobots.flush(timeoutMs?)` | **Entrega já** o que a execução fez: as
|
|
118
|
-
| `hobots.
|
|
119
|
-
| `hobots.image(bytes \| caminho, { caption?, contentType? })` | Embrulha um **print** (PNG/JPEG/WEBP, até 5 MB, 20 por execução) para ir junto de um log, item ou
|
|
117
|
+
| `hobots.flush(timeoutMs?)` | **Entrega já** o que a execução fez: as etapas raiz finalizadas viram transactions parciais (waterfall na Central sem esperar a run terminar) e itens, logs e progresso pendentes saem sem esperar o throttle. `step` (raiz) e cada ciclo do `items.each` já chamam sozinhos; à mão, para o resto da rodada de um agente `start()`. Aguarda com teto (15 s) e nunca lança. |
|
|
118
|
+
| `hobots.step(nome, async etapa => …, { retries = 1, retryDelayMs = 5_000, flush = true }?)` | Uma **etapa** no waterfall de performance da execução: roda a função, **tenta de novo** se ela lançar (`retries: 0` desliga — use em etapas não idempotentes) e fecha a etapa. Resolve com o retorno da função. `hobots.step` dentro de outra vira sub-etapa. A etapa raiz faz `flush()` ao terminar (`flush: false` desliga). O handle `etapa` tem `warning(motivo, { image }?)`, `image(png)` e `data(chave, valor)`. |
|
|
119
|
+
| `hobots.image(bytes \| caminho, { caption?, contentType? })` | Embrulha um **print** (PNG/JPEG/WEBP, até 5 MB, 20 por execução) para ir junto de um log, item ou etapa. Puro: o upload roda em segundo plano quando o evento é registrado; nunca lança nem bloqueia. |
|
|
120
120
|
| `hobots.captureException(erro, hint?)` | Abre uma **issue** (módulo Issues). `hint.attachment` anexa um print à linha de log correspondente. |
|
|
121
121
|
| `hobots.retry(fn, { retries = 3, delayMs = 1_000, shouldRetry? })` | async-retry ciente do cancelamento da execução. `delayMs` aceita função `(tentativa) => ms`. |
|
|
122
122
|
| `hobots.sleep(ms)` | Espera **cancelável**: rejeita com `RunCanceled` se a execução for cancelada. |
|
|
@@ -162,7 +162,7 @@ hobots.register(async () => {
|
|
|
162
162
|
|
|
163
163
|
Uma execução cancelada no app **não mata** o handler — o SDK dispara `hobots.signal` e o próximo ponto de parada lança **`RunCanceled`**:
|
|
164
164
|
|
|
165
|
-
- `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.
|
|
165
|
+
- `hobots.items.*`, `hobots.progress.total/advance/set`, `hobots.step()`, `hobots.sleep()` e cada tentativa de `hobots.retry()`.
|
|
166
166
|
- **Nunca `hobots.log.*`** — um log dentro de um `finally` de limpeza não pode explodir.
|
|
167
167
|
|
|
168
168
|
Num loop de itens, isso costuma bastar: registrar o item processado já é o ponto de parada. Para controle fino, `hobots.throwIfCanceled()` e `hobots.signal` continuam disponíveis. Quando o cancelamento veio do app, o `RunCanceled` é **suprimido** — a execução já está `cancelada` no servidor e nada é reportado por cima. Limpeza (fechar navegador, desfazer estado) vai em `try/finally`.
|
|
@@ -231,17 +231,20 @@ Sem handler próprio de sinal, um `SIGINT`/`SIGTERM` (ou erro fatal) com execuç
|
|
|
231
231
|
|
|
232
232
|
## Changelog resumido
|
|
233
233
|
|
|
234
|
+
- **5.0.0** — **uma API só de etapas** (breaking): `hobots.step(nome, fn, { retries, retryDelayMs }?)` abre a etapa, tenta de novo quando `fn` lança (default 1 tentativa extra) e fecha; `fn` recebe o handle `etapa` (`warning`/`image`/`data`); `hobots.step` dentro de outra (ou do handler do `items.each`) vira sub-etapa. `hobots.transaction`, `ctx.transaction` e os tipos `Transaction`/`Step`/`StepOutcomeOptions` foram removidos; `item.step` é o handle novo. Modo local ecoa `[hobots:step] FINISH …`. Nomes de etapa são recortados em 2048 no cliente. Flush automático ao fim da etapa raiz e de cada ciclo do `items.each` (`flush: false` desliga). Antes/depois no [CHANGELOG](./CHANGELOG.md).
|
|
235
|
+
- **4.6.0** — **modo local** (`HOBOTS_LOCAL_RUN=1`): `register()`/`start()` rodam o handler uma vez sem Central, sem secret e sem rede, com params de `HOBOTS_RUN_PARAMS`; itens viram linhas `[hobots:item]` + `[hobots:summary]` no stdout, exit code 0/1/130; CLI `npx hobots run <entry.js> [--params] [--param] [--file] [--reset-ledger]`; `FileRef` local aceita `path` no modo local; eco `[hobots:progress] c/t` no stdout (paridade com o SDK Python).
|
|
236
|
+
|
|
234
237
|
- **4.3.0** — `register()` com env `HOBOTS_RUN_ID` (ou opção `runId`) executa só essa run: `POST /v1/runs/{id}/start` no lugar do poll, handler uma vez e `close()`; tipos `RunStartRequest`/`RunStartResponse` em `hobots/protocol`; itens recusados por armazenamento cheio (`402 storage_limit`) viram um aviso só por episódio, sem parar os envios.
|
|
235
238
|
- **4.2.0** — **arquivos do formulário**: `hobots.files` (`list`, `get`, `download`, `downloadAll`, `read`) e `isFileRef`; tipos `FileRef`, `FileStorage`, `FileDownloadResponse` em `hobots/protocol`.
|
|
236
239
|
- **4.0.0** — **o agente é a própria tarefa** (breaking): `register(handler, options?)`/`start(handler, options?)` **sem slug**; **uma chamada por processo** (a segunda lança); `hobots.task`/`ctx.task` removidos; tipos `TaskHandler`→`RunHandler`, `TaskOptions`→`RunOptions`, `TaskItemsOptions`→`ItemsOptions`; wire: `InstanceInfo.mode` (`on_demand` \| `always_on`) e `InstanceInfo.items` no lugar de `tasks`/`task_items`, endpoints em `/v1/runs/*`; eco no terminal `[hobots] INFO …`. Guia de migração no [CHANGELOG](./CHANGELOG.md).
|
|
237
|
-
- **3.4.0** — `hobots.attach()` removido; o print vai junto do evento: `hobots.image(...)` no último argumento de `log.*`, `{ image }` em itens e
|
|
240
|
+
- **3.4.0** — `hobots.attach()` removido; o print vai junto do evento: `hobots.image(...)` no último argumento de `log.*`, `{ image }` em itens e etapas.
|
|
238
241
|
- **3.1.0** — `hobots.log.*` aceita vários argumentos (como `console.log`); `retries` inválido (`NaN`) cai no default em vez de tentar para sempre; 429 de itens e 429/501/503 de anexo são definitivos (sem esperar a ladder — `quota`/`unsupported` passam a funcionar de fato); timeout **por requisição** nos anexos; truncamento nos limites do servidor (tag 512, fingerprint 10×255, nome de transaction 500, 200 steps, `exception.value` 8192, `filename` 1024); PNG acima de 20 000 px não declara dimensões; PATCH terminal nunca passa de 100 logs.
|
|
239
242
|
|
|
240
243
|
## Documentação
|
|
241
244
|
|
|
242
245
|
- [Getting started](./docs/getting-started.md) — instalação, envs, o primeiro agente nos dois modos.
|
|
243
246
|
- [Execuções](./docs/runs.md) — handler, `hobots.*`, itens, progresso, anexos, cancelamento, retry, concorrência, idempotência.
|
|
244
|
-
- [Conceitos](./docs/concepts.md) — o vocabulário: agente, execução, evento,
|
|
247
|
+
- [Conceitos](./docs/concepts.md) — o vocabulário: agente, execução, evento, etapa, heartbeat, instância, claim…
|
|
245
248
|
- [Referência de API](./docs/api-reference.md) — assinaturas, tabelas de opções e tempos.
|
|
246
249
|
- [Protocolo & transporte](./docs/protocol.md) — endpoints, envelope, retries, claim.
|
|
247
250
|
- Exemplos runnable em [`packages/hobots-sdk-demos`](../hobots-sdk-demos).
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/cli.ts
|
|
22
|
+
var cli_exports = {};
|
|
23
|
+
__export(cli_exports, {
|
|
24
|
+
CliError: () => CliError,
|
|
25
|
+
LEDGER_RESET_ENV: () => LEDGER_RESET_ENV,
|
|
26
|
+
USAGE: () => USAGE,
|
|
27
|
+
childEnv: () => childEnv,
|
|
28
|
+
contentTypeFor: () => contentTypeFor,
|
|
29
|
+
makeLocalFileRef: () => makeLocalFileRef,
|
|
30
|
+
parseParamValue: () => parseParamValue,
|
|
31
|
+
parseRunArgs: () => parseRunArgs,
|
|
32
|
+
runCli: () => runCli,
|
|
33
|
+
signalExitCode: () => signalExitCode,
|
|
34
|
+
wantsHelp: () => wantsHelp
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(cli_exports);
|
|
37
|
+
var import_node_child_process = require("child_process");
|
|
38
|
+
var import_node_crypto = require("crypto");
|
|
39
|
+
var import_node_fs = require("fs");
|
|
40
|
+
var import_node_os = require("os");
|
|
41
|
+
var import_node_path = require("path");
|
|
42
|
+
|
|
43
|
+
// src/env.ts
|
|
44
|
+
var LOCAL_RUN_ENV = "HOBOTS_LOCAL_RUN";
|
|
45
|
+
var RUN_PARAMS_ENV = "HOBOTS_RUN_PARAMS";
|
|
46
|
+
|
|
47
|
+
// src/cli.ts
|
|
48
|
+
var LEDGER_RESET_ENV = "HOBOTS_LEDGER_RESET";
|
|
49
|
+
var USAGE = "uso: hobots run <entry.js> [--params <arquivo.json>] [--param <chave>=<valor>]... [--file <campo>=<caminho>]... [--reset-ledger] [-- <args do bot>]";
|
|
50
|
+
var CONTENT_TYPES = {
|
|
51
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
52
|
+
xls: "application/vnd.ms-excel",
|
|
53
|
+
csv: "text/csv",
|
|
54
|
+
ods: "application/vnd.oasis.opendocument.spreadsheet",
|
|
55
|
+
pdf: "application/pdf",
|
|
56
|
+
json: "application/json",
|
|
57
|
+
txt: "text/plain",
|
|
58
|
+
xml: "application/xml",
|
|
59
|
+
png: "image/png",
|
|
60
|
+
jpg: "image/jpeg",
|
|
61
|
+
jpeg: "image/jpeg",
|
|
62
|
+
zip: "application/zip"
|
|
63
|
+
};
|
|
64
|
+
function contentTypeFor(name) {
|
|
65
|
+
return CONTENT_TYPES[(0, import_node_path.extname)(name).slice(1).toLowerCase()] ?? "application/octet-stream";
|
|
66
|
+
}
|
|
67
|
+
var CliError = class extends Error {
|
|
68
|
+
constructor(message, usage = false) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.usage = usage;
|
|
71
|
+
this.name = "CliError";
|
|
72
|
+
}
|
|
73
|
+
usage;
|
|
74
|
+
};
|
|
75
|
+
function parseParamValue(raw) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(raw);
|
|
78
|
+
} catch {
|
|
79
|
+
return raw;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function splitAssignment(flag, raw) {
|
|
83
|
+
const at = raw.indexOf("=");
|
|
84
|
+
if (at <= 0) throw new CliError(`${flag} espera <chave>=<valor>`, true);
|
|
85
|
+
return [raw.slice(0, at), raw.slice(at + 1)];
|
|
86
|
+
}
|
|
87
|
+
function isRegularFile(path) {
|
|
88
|
+
try {
|
|
89
|
+
return (0, import_node_fs.statSync)(path).isFile();
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function readParamsFile(given, path) {
|
|
95
|
+
if (!isRegularFile(path)) throw new CliError(`--params: arquivo n\xE3o encontrado: ${given}`);
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
99
|
+
} catch {
|
|
100
|
+
throw new CliError("--params: n\xE3o \xE9 JSON v\xE1lido");
|
|
101
|
+
}
|
|
102
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
103
|
+
throw new CliError("--params: esperado um objeto JSON");
|
|
104
|
+
}
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
function makeLocalFileRef(path, cwd) {
|
|
108
|
+
const absolute = (0, import_node_path.resolve)(cwd, path);
|
|
109
|
+
if (!isRegularFile(absolute)) throw new CliError(`arquivo n\xE3o encontrado: ${path}`);
|
|
110
|
+
return {
|
|
111
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
112
|
+
name: (0, import_node_path.basename)(absolute),
|
|
113
|
+
size: (0, import_node_fs.statSync)(absolute).size,
|
|
114
|
+
contentType: contentTypeFor(absolute),
|
|
115
|
+
storage: "local",
|
|
116
|
+
path: absolute
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function wantsHelp(argv) {
|
|
120
|
+
const end = argv.indexOf("--");
|
|
121
|
+
return (end === -1 ? argv : argv.slice(0, end)).some((arg) => arg === "-h" || arg === "--help");
|
|
122
|
+
}
|
|
123
|
+
function parseRunArgs(argv, cwd = process.cwd()) {
|
|
124
|
+
if (argv[0] !== "run") {
|
|
125
|
+
throw new CliError(argv[0] === void 0 ? "comando ausente" : `comando desconhecido: ${argv[0]}`, true);
|
|
126
|
+
}
|
|
127
|
+
let entry;
|
|
128
|
+
let fromFile = {};
|
|
129
|
+
const fromFlags = {};
|
|
130
|
+
const fileFields = /* @__PURE__ */ new Map();
|
|
131
|
+
let resetLedger = false;
|
|
132
|
+
let botArgs = [];
|
|
133
|
+
const args = argv.slice(1);
|
|
134
|
+
for (let i = 0; i < args.length; i++) {
|
|
135
|
+
const arg = args[i];
|
|
136
|
+
if (arg === "--") {
|
|
137
|
+
botArgs = args.slice(i + 1);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
if (arg === "--reset-ledger") {
|
|
141
|
+
resetLedger = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (arg.startsWith("--")) {
|
|
145
|
+
const eq = arg.indexOf("=");
|
|
146
|
+
const flag = eq === -1 ? arg : arg.slice(0, eq);
|
|
147
|
+
if (flag !== "--params" && flag !== "--param" && flag !== "--file") throw new CliError(`flag desconhecida: ${flag}`, true);
|
|
148
|
+
const value = eq === -1 ? args[++i] : arg.slice(eq + 1);
|
|
149
|
+
if (value === void 0) throw new CliError(`${flag} espera um valor`, true);
|
|
150
|
+
if (flag === "--params") {
|
|
151
|
+
fromFile = readParamsFile(value, (0, import_node_path.resolve)(cwd, value));
|
|
152
|
+
} else if (flag === "--param") {
|
|
153
|
+
const [key, raw] = splitAssignment(flag, value);
|
|
154
|
+
fromFlags[key] = parseParamValue(raw);
|
|
155
|
+
} else {
|
|
156
|
+
const [field, path] = splitAssignment(flag, value);
|
|
157
|
+
const refs = fileFields.get(field) ?? [];
|
|
158
|
+
refs.push(makeLocalFileRef(path, cwd));
|
|
159
|
+
fileFields.set(field, refs);
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (arg.startsWith("-") && arg !== "-") throw new CliError(`flag desconhecida: ${arg}`, true);
|
|
164
|
+
if (entry !== void 0) throw new CliError(`argumento inesperado: ${arg} (use -- antes dos argumentos do bot)`, true);
|
|
165
|
+
entry = arg;
|
|
166
|
+
}
|
|
167
|
+
if (entry === void 0) throw new CliError("entry do bot ausente", true);
|
|
168
|
+
const entryPath = (0, import_node_path.resolve)(cwd, entry);
|
|
169
|
+
if (!isRegularFile(entryPath)) throw new CliError(`arquivo n\xE3o encontrado: ${entry}`);
|
|
170
|
+
const params = { ...fromFile, ...fromFlags };
|
|
171
|
+
for (const [field, refs] of fileFields) params[field] = refs.length === 1 ? refs[0] : refs;
|
|
172
|
+
return { entry: entryPath, params, resetLedger, botArgs };
|
|
173
|
+
}
|
|
174
|
+
function childEnv(command, base = process.env) {
|
|
175
|
+
const env = { ...base, [LOCAL_RUN_ENV]: "1", [RUN_PARAMS_ENV]: JSON.stringify(command.params) };
|
|
176
|
+
if (command.resetLedger) env[LEDGER_RESET_ENV] = "1";
|
|
177
|
+
return env;
|
|
178
|
+
}
|
|
179
|
+
function signalExitCode(signal) {
|
|
180
|
+
const number = signal ? import_node_os.constants.signals[signal] : void 0;
|
|
181
|
+
return number === void 0 ? 1 : 128 + number;
|
|
182
|
+
}
|
|
183
|
+
var defaultDeps = () => ({
|
|
184
|
+
spawn: import_node_child_process.spawn,
|
|
185
|
+
stdout: (line) => console.log(line),
|
|
186
|
+
stderr: (line) => console.error(line),
|
|
187
|
+
execPath: process.execPath,
|
|
188
|
+
cwd: process.cwd(),
|
|
189
|
+
env: process.env,
|
|
190
|
+
signals: process
|
|
191
|
+
});
|
|
192
|
+
async function runCli(argv, overrides = {}) {
|
|
193
|
+
const deps = { ...defaultDeps(), ...overrides };
|
|
194
|
+
if (wantsHelp(argv)) {
|
|
195
|
+
deps.stdout(USAGE);
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
let command;
|
|
199
|
+
try {
|
|
200
|
+
command = parseRunArgs(argv, deps.cwd);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (!(error instanceof CliError)) throw error;
|
|
203
|
+
deps.stderr(`[hobots] ERROR ${error.message}`);
|
|
204
|
+
if (error.usage) deps.stderr(USAGE);
|
|
205
|
+
return 2;
|
|
206
|
+
}
|
|
207
|
+
return new Promise((resolveCode) => {
|
|
208
|
+
const child = deps.spawn(deps.execPath, [command.entry, ...command.botArgs], {
|
|
209
|
+
stdio: "inherit",
|
|
210
|
+
env: childEnv(command, deps.env),
|
|
211
|
+
cwd: deps.cwd
|
|
212
|
+
});
|
|
213
|
+
const forward = (signal) => () => void child.kill(signal);
|
|
214
|
+
const onInt = forward("SIGINT");
|
|
215
|
+
const onTerm = forward("SIGTERM");
|
|
216
|
+
deps.signals.on("SIGINT", onInt);
|
|
217
|
+
deps.signals.on("SIGTERM", onTerm);
|
|
218
|
+
const cleanup = () => {
|
|
219
|
+
deps.signals.off("SIGINT", onInt);
|
|
220
|
+
deps.signals.off("SIGTERM", onTerm);
|
|
221
|
+
};
|
|
222
|
+
child.once("error", (error) => {
|
|
223
|
+
cleanup();
|
|
224
|
+
deps.stderr(`[hobots] ERROR n\xE3o foi poss\xEDvel executar ${command.entry}: ${error.message}`);
|
|
225
|
+
resolveCode(2);
|
|
226
|
+
});
|
|
227
|
+
child.once("exit", (code, signal) => {
|
|
228
|
+
cleanup();
|
|
229
|
+
resolveCode(code ?? signalExitCode(signal));
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) {
|
|
234
|
+
void runCli(process.argv.slice(2)).then((code) => process.exit(code));
|
|
235
|
+
}
|
|
236
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
237
|
+
0 && (module.exports = {
|
|
238
|
+
CliError,
|
|
239
|
+
LEDGER_RESET_ENV,
|
|
240
|
+
USAGE,
|
|
241
|
+
childEnv,
|
|
242
|
+
contentTypeFor,
|
|
243
|
+
makeLocalFileRef,
|
|
244
|
+
parseParamValue,
|
|
245
|
+
parseRunArgs,
|
|
246
|
+
runCli,
|
|
247
|
+
signalExitCode,
|
|
248
|
+
wantsHelp
|
|
249
|
+
});
|
|
250
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/env.ts"],"sourcesContent":["/**\n * `npx hobots run <entry.js> [opções] [-- args do bot]` — executa o bot em MODO LOCAL (teste sem a\n * Central): monta os params (`--params`, `--param`, `--file`), define `HOBOTS_LOCAL_RUN=1` e\n * `HOBOTS_RUN_PARAMS` e roda o entry com o mesmo Node (`process.execPath`), herdando o terminal.\n * O exit code do bot é o da CLI. Espelho da `python -m hobots run` do SDK Python.\n */\nimport { spawn as nodeSpawn, type ChildProcess } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, statSync } from 'node:fs';\nimport { constants as osConstants } from 'node:os';\nimport { basename, extname, resolve } from 'node:path';\nimport { LOCAL_RUN_ENV, RUN_PARAMS_ENV } from './env';\nimport type { FileRef } from './tasks/protocol';\n\n/** `--reset-ledger`: o scaffold do bot (não o SDK) lê esta env e zera o ledger antes de começar. */\nexport const LEDGER_RESET_ENV = 'HOBOTS_LEDGER_RESET';\n\nexport const USAGE =\n 'uso: hobots run <entry.js> [--params <arquivo.json>] [--param <chave>=<valor>]... [--file <campo>=<caminho>]... [--reset-ledger] [-- <args do bot>]';\n\nconst CONTENT_TYPES: Record<string, string> = {\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n xls: 'application/vnd.ms-excel',\n csv: 'text/csv',\n ods: 'application/vnd.oasis.opendocument.spreadsheet',\n pdf: 'application/pdf',\n json: 'application/json',\n txt: 'text/plain',\n xml: 'application/xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n zip: 'application/zip',\n};\n\n/** Content-type pela extensão (minúsculas); desconhecida → `application/octet-stream`. */\nexport function contentTypeFor(name: string): string {\n return CONTENT_TYPES[extname(name).slice(1).toLowerCase()] ?? 'application/octet-stream';\n}\n\n/** Erro de uso ou de ambiente da CLI: mensagem curta no stderr e exit 2. `usage` acrescenta a linha de uso. */\nexport class CliError extends Error {\n constructor(\n message: string,\n readonly usage = false\n ) {\n super(message);\n this.name = 'CliError';\n }\n}\n\n/** FileRef `local` do modo local: a chave extra `path` aponta para o arquivo (preservado no download). */\nexport interface LocalFileRef extends FileRef {\n storage: 'local';\n path: string;\n}\n\nexport interface RunCommand {\n /** Caminho absoluto do entry do bot. */\n entry: string;\n params: Record<string, unknown>;\n resetLedger: boolean;\n /** Argumentos depois de `--`, repassados ao bot. */\n botArgs: string[];\n}\n\n/** `--param chave=valor`: JSON válido vira o valor parseado (`3`, `true`, `\"x\"`, `[1]`); senão, a string crua. */\nexport function parseParamValue(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\nfunction splitAssignment(flag: string, raw: string): [string, string] {\n const at = raw.indexOf('=');\n if (at <= 0) throw new CliError(`${flag} espera <chave>=<valor>`, true);\n return [raw.slice(0, at), raw.slice(at + 1)];\n}\n\nfunction isRegularFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nfunction readParamsFile(given: string, path: string): Record<string, unknown> {\n if (!isRegularFile(path)) throw new CliError(`--params: arquivo não encontrado: ${given}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf8'));\n } catch {\n throw new CliError('--params: não é JSON válido');\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new CliError('--params: esperado um objeto JSON');\n }\n return parsed as Record<string, unknown>;\n}\n\nexport function makeLocalFileRef(path: string, cwd: string): LocalFileRef {\n const absolute = resolve(cwd, path);\n if (!isRegularFile(absolute)) throw new CliError(`arquivo não encontrado: ${path}`);\n return {\n id: randomUUID(),\n name: basename(absolute),\n size: statSync(absolute).size,\n contentType: contentTypeFor(absolute),\n storage: 'local',\n path: absolute,\n };\n}\n\n/**\n * `hobots run <entry> …`. Precedência dos params: `--params` < `--param` < `--file`; o mesmo\n * campo repetido em `--file` vira uma lista de FileRefs. Flags aceitam `--flag valor` e `--flag=valor`.\n */\n/** `true` quando a linha pede ajuda (`-h`/`--help` em qualquer posição antes do `--`). */\nexport function wantsHelp(argv: string[]): boolean {\n const end = argv.indexOf('--');\n return (end === -1 ? argv : argv.slice(0, end)).some(arg => arg === '-h' || arg === '--help');\n}\n\nexport function parseRunArgs(argv: string[], cwd = process.cwd()): RunCommand {\n if (argv[0] !== 'run') {\n throw new CliError(argv[0] === undefined ? 'comando ausente' : `comando desconhecido: ${argv[0]}`, true);\n }\n let entry: string | undefined;\n let fromFile: Record<string, unknown> = {};\n const fromFlags: Record<string, unknown> = {};\n const fileFields = new Map<string, LocalFileRef[]>();\n let resetLedger = false;\n let botArgs: string[] = [];\n\n const args = argv.slice(1);\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === '--') {\n botArgs = args.slice(i + 1);\n break;\n }\n if (arg === '--reset-ledger') {\n resetLedger = true;\n continue;\n }\n if (arg.startsWith('--')) {\n const eq = arg.indexOf('=');\n const flag = eq === -1 ? arg : arg.slice(0, eq);\n if (flag !== '--params' && flag !== '--param' && flag !== '--file') throw new CliError(`flag desconhecida: ${flag}`, true);\n const value = eq === -1 ? args[++i] : arg.slice(eq + 1);\n if (value === undefined) throw new CliError(`${flag} espera um valor`, true);\n if (flag === '--params') {\n fromFile = readParamsFile(value, resolve(cwd, value));\n } else if (flag === '--param') {\n const [key, raw] = splitAssignment(flag, value);\n fromFlags[key] = parseParamValue(raw);\n } else {\n const [field, path] = splitAssignment(flag, value);\n const refs = fileFields.get(field) ?? [];\n refs.push(makeLocalFileRef(path, cwd));\n fileFields.set(field, refs);\n }\n continue;\n }\n if (arg.startsWith('-') && arg !== '-') throw new CliError(`flag desconhecida: ${arg}`, true);\n if (entry !== undefined) throw new CliError(`argumento inesperado: ${arg} (use -- antes dos argumentos do bot)`, true);\n entry = arg;\n }\n if (entry === undefined) throw new CliError('entry do bot ausente', true);\n const entryPath = resolve(cwd, entry);\n if (!isRegularFile(entryPath)) throw new CliError(`arquivo não encontrado: ${entry}`);\n\n const params: Record<string, unknown> = { ...fromFile, ...fromFlags };\n for (const [field, refs] of fileFields) params[field] = refs.length === 1 ? refs[0] : refs;\n return { entry: entryPath, params, resetLedger, botArgs };\n}\n\n/** Env do processo do bot: a do pai + o gatilho do modo local, os params e (opcional) o reset do ledger. */\nexport function childEnv(command: RunCommand, base: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...base, [LOCAL_RUN_ENV]: '1', [RUN_PARAMS_ENV]: JSON.stringify(command.params) };\n if (command.resetLedger) env[LEDGER_RESET_ENV] = '1';\n return env;\n}\n\n/** Filho morto por sinal: 128 + número do sinal (SIGINT → 130, SIGTERM → 143), como o shell. */\nexport function signalExitCode(signal: NodeJS.Signals | null): number {\n const number: number | undefined = signal ? (osConstants.signals as Record<string, number | undefined>)[signal] : undefined;\n return number === undefined ? 1 : 128 + number;\n}\n\nexport type Spawn = (command: string, args: string[], options: { stdio: 'inherit'; env: NodeJS.ProcessEnv; cwd: string }) => ChildProcess;\n\nexport interface CliDeps {\n spawn: Spawn;\n stdout: (line: string) => void;\n stderr: (line: string) => void;\n execPath: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n /** Onde instalar o repasse de SIGINT/SIGTERM ao filho (o `process`). */\n signals: Pick<NodeJS.Process, 'on' | 'off'>;\n}\n\nconst defaultDeps = (): CliDeps => ({\n spawn: nodeSpawn,\n stdout: line => console.log(line),\n stderr: line => console.error(line),\n execPath: process.execPath,\n cwd: process.cwd(),\n env: process.env,\n signals: process,\n});\n\n/** Roda a CLI e resolve com o exit code (2 em erro de uso/ambiente; senão o do bot). Nunca chama `process.exit`. */\nexport async function runCli(argv: string[], overrides: Partial<CliDeps> = {}): Promise<number> {\n const deps = { ...defaultDeps(), ...overrides };\n if (wantsHelp(argv)) {\n deps.stdout(USAGE);\n return 0;\n }\n let command: RunCommand;\n try {\n command = parseRunArgs(argv, deps.cwd);\n } catch (error) {\n if (!(error instanceof CliError)) throw error;\n deps.stderr(`[hobots] ERROR ${error.message}`);\n if (error.usage) deps.stderr(USAGE);\n return 2;\n }\n\n return new Promise<number>(resolveCode => {\n const child = deps.spawn(deps.execPath, [command.entry, ...command.botArgs], {\n stdio: 'inherit',\n env: childEnv(command, deps.env),\n cwd: deps.cwd,\n });\n // Ctrl+C chega aos dois pelo terminal; repassar cobre o `kill` só no pai — e o pai espera o\n // bot imprimir o `[hobots:summary]` e sair com 130 em vez de morrer na frente dele. A CLI\n // nunca mata o filho por conta própria: quem decide o fim é o bot (graça de 4 s do SDK)\n const forward = (signal: NodeJS.Signals) => (): void => void child.kill(signal);\n const onInt = forward('SIGINT');\n const onTerm = forward('SIGTERM');\n deps.signals.on('SIGINT', onInt);\n deps.signals.on('SIGTERM', onTerm);\n const cleanup = (): void => {\n deps.signals.off('SIGINT', onInt);\n deps.signals.off('SIGTERM', onTerm);\n };\n child.once('error', error => {\n cleanup();\n deps.stderr(`[hobots] ERROR não foi possível executar ${command.entry}: ${error.message}`);\n resolveCode(2);\n });\n child.once('exit', (code, signal) => {\n cleanup();\n resolveCode(code ?? signalExitCode(signal));\n });\n });\n}\n\n/* v8 ignore start -- ponto de entrada do binário (dist/cli.cjs executado diretamente); a suíte importa o módulo */\nif (typeof require !== 'undefined' && typeof module !== 'undefined' && require.main === module) {\n void runCli(process.argv.slice(2)).then(code => process.exit(code));\n}\n/* v8 ignore stop */\n","import type { EnvironmentInput } from './environment-protocol';\nimport { isNode } from './utils';\n\nexport const INSTANCE_ID_ENV = 'HOBOTS_INSTANCE_ID';\nexport const ENVIRONMENT_ENV = 'HOBOTS_ENVIRONMENT';\nexport const RUN_ID_ENV = 'HOBOTS_RUN_ID';\n/** Modo local (teste sem a Central): `HOBOTS_LOCAL_RUN=1` — handler uma vez, sem rede, params de `HOBOTS_RUN_PARAMS`. */\nexport const LOCAL_RUN_ENV = 'HOBOTS_LOCAL_RUN';\n/** Params da execução no modo local: um objeto JSON (ausente/vazio = `{}`). */\nexport const RUN_PARAMS_ENV = 'HOBOTS_RUN_PARAMS';\n\nlet dotenvLoaded = false;\n\n/**\n * Carrega o `.env` da pasta de trabalho (`process.cwd()`) em `process.env`, uma vez por\n * processo: o bot não precisa de `dotenv` nem de `--env-file` para o SDK (e o próprio\n * código do bot) acharem `HOBOTS_CLIENT_SECRET` & cia. Usa o parser nativo do Node\n * (`process.loadEnvFile`, Node ≥ 20.12) — síncrono, porque `register()`/`start()` resolvem\n * as credenciais na hora. A env já definida no ambiente **vence** o arquivo. Sem `.env`\n * (ou em runtime sem `loadEnvFile`), não faz nada; erro de leitura vira um aviso que cita\n * só o código do erro, nunca o conteúdo do arquivo.\n */\nexport function loadDotenv(): void {\n if (dotenvLoaded) return;\n dotenvLoaded = true;\n if (!isNode() || typeof process.loadEnvFile !== 'function') return;\n try {\n process.loadEnvFile('.env');\n } catch (err) {\n const code = (err as { code?: unknown } | null)?.code;\n if (code === 'ENOENT') return;\n console.warn(`[hobots] não foi possível ler o .env da pasta de trabalho (${typeof code === 'string' ? code : 'erro desconhecido'}) — seguindo só com as variáveis do ambiente`);\n }\n}\n\n/** Só para testes: permite carregar o `.env` de novo. */\nexport function _resetDotenvForTesting(): void {\n dotenvLoaded = false;\n}\n\n/** instanceId da opção ou da env — sem os dois, `undefined` (o fallback é o hostname, assíncrono). */\nexport function resolveInstanceId(option: string | undefined): string | undefined {\n const fromOption = option?.trim();\n if (fromOption) return fromOption;\n if (typeof process === 'undefined' || !process.env) return undefined;\n return process.env[INSTANCE_ID_ENV]?.trim() || undefined;\n}\n\n/**\n * Run a que o register() se anexa (opção → env HOBOTS_RUN_ID). Sem as duas, `undefined`:\n * o agente segue no poll de sempre.\n */\nexport function resolveRunId(option: string | undefined): string | undefined {\n const fromOption = option?.trim();\n if (fromOption) return fromOption;\n if (typeof process === 'undefined' || !process.env) return undefined;\n return process.env[RUN_ID_ENV]?.trim() || undefined;\n}\n\n/** Modo local ligado (`HOBOTS_LOCAL_RUN=1`): sem Central, sem secret, sem rede. */\nexport function isLocalRun(): boolean {\n if (typeof process === 'undefined' || !process.env) return false;\n return process.env[LOCAL_RUN_ENV]?.trim() === '1';\n}\n\n/**\n * Params do modo local: JSON de `HOBOTS_RUN_PARAMS` (ausente ou vazio = `{}`). Lança com o\n * motivo quando o valor não é um objeto JSON válido — quem chama imprime e encerra com código 2.\n */\nexport function resolveLocalRunParams(): Record<string, unknown> {\n const raw = process.env[RUN_PARAMS_ENV]?.trim();\n if (!raw) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`${RUN_PARAMS_ENV} inválido: não é JSON válido`);\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`${RUN_PARAMS_ENV} inválido: esperado um objeto JSON`);\n }\n return parsed as Record<string, unknown>;\n}\n\n/** environment da opção ou da env — sem os dois, `undefined` (quem decide o default 'production' é o Client). */\nexport function resolveEnvironmentOption(option: EnvironmentInput | undefined): EnvironmentInput | undefined {\n const fromOption = option?.trim();\n if (fromOption) return fromOption;\n if (typeof process === 'undefined' || !process.env) return undefined;\n return process.env[ENVIRONMENT_ENV]?.trim() || undefined;\n}\n\n/**\n * Último recurso para a identidade da instância: o hostname da máquina. Import dinâmico\n * de node:os (o bundle é neutro); fora de Node, 'browser'.\n */\nexport async function fallbackInstanceId(): Promise<string> {\n if (!isNode()) return 'browser';\n try {\n const os = await import('node:os');\n return os.hostname() || 'unknown-host';\n } catch {\n return 'unknown-host';\n }\n}\n"],"mappings":"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","names":["osConstants","nodeSpawn"]}
|