dualisia-cli 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +136 -74
  2. package/bin/dualisia.mjs +163 -155
  3. package/package.json +46 -37
package/README.md CHANGED
@@ -1,74 +1,136 @@
1
- # dualisia-cli
2
-
3
- CLI oficial do [Dualisia](https://dualisia.com.br): apura CBS e IBS a partir de
4
- XMLs de NF-e e consulta os dados do escritório pela API REST.
5
-
6
- > **Status**: o pacote está pronto e testado, mas **ainda não foi publicado** em
7
- > registro público. Publicar exige credencial de npm — ver
8
- > "Publicação" abaixo.
9
-
10
- ## Instalação
11
-
12
- Enquanto não publicação, use direto do repositório:
13
-
14
- ```bash
15
- node ./packages/cli/bin/dualisia.mjs --help
16
- ```
17
-
18
- Depois de publicado:
19
-
20
- ```bash
21
- npm install -g dualisia-cli
22
- ```
23
-
24
- ## Uso
25
-
26
- ```bash
27
- # Apura CBS e IBS de uma nota (público, sem chave)
28
- dualisia apurar nota.xml --ano 2027
29
-
30
- # Projeta a carga ano a ano
31
- dualisia simular nota.xml --anos 2026,2029,2033
32
-
33
- # Dados do escritório (exige API key)
34
- export DUALISIA_API_KEY=dlai_live_xxx
35
- dualisia clientes --limit 10
36
- dualisia documentos --from 2026-01-01 --to 2026-12-31
37
- ```
38
-
39
- `--json` imprime a resposta bruta da API, útil para encadear com `jq`.
40
-
41
- ## Variáveis de ambiente
42
-
43
- | Variável | Para que serve |
44
- | --- | --- |
45
- | `DUALISIA_API_KEY` | API key da organização (`dlai_live_...`). |
46
- | `DUALISIA_BASE_URL` | Origem da API. Padrão: `https://dualisia.com.br`. |
47
-
48
- ## Códigos de saída
49
-
50
- | Código | Significado |
51
- | --- | --- |
52
- | `0` | Sucesso. |
53
- | `1` | Erro de uso ou erro 4xx da API. |
54
- | `2` | Erro 5xx da API. |
55
-
56
- ## Publicação
57
-
58
- O pacote não tem etapa de build: publica os arquivos como estão.
59
-
60
- ```bash
61
- cd packages/cli
62
- npm publish
63
- ```
64
-
65
- Requer um token de npm com permissão de publicação. O pacote não tem escopo,
66
- então qualquer conta autenticada que seja dona do nome publica.
67
-
68
- ## Documentação da API
69
-
70
- <https://dualisia.com.br/developers> · <https://dualisia.com.br/openapi.json>
71
-
72
- ## Licença
73
-
74
- MIT ver [LICENSE](./LICENSE).
1
+ # dualisia-cli
2
+
3
+ [![npm](https://img.shields.io/npm/v/dualisia-cli)](https://www.npmjs.com/package/dualisia-cli)
4
+ [![CI](https://github.com/munhoz-iago/dualisia-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/munhoz-iago/dualisia-cli/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/dualisia-cli)](./LICENSE)
6
+
7
+ CLI oficial do [Dualisia](https://dualisia.com.br): apura **CBS** e **IBS** a
8
+ partir de XMLs de NF-e e consulta os dados do escritório pela API REST.
9
+
10
+ Entre 2026 e 2033 a Reforma Tributária brasileira roda em transição: o regime
11
+ atual continua gerando a obrigação enquanto os tributos novos (CBS e IBS,
12
+ LC 214/2025) precisam ser apurados em paralelo. Este CLI põe essa apuração no
13
+ terminal — para conferir uma nota, encadear num script ou deixar um agente
14
+ chamar sem escrever integração.
15
+
16
+ ## Instalação
17
+
18
+ ```bash
19
+ npm install -g dualisia-cli
20
+ ```
21
+
22
+ Ou sem instalar nada:
23
+
24
+ ```bash
25
+ npx dualisia-cli --help
26
+ ```
27
+
28
+ Requer Node.js 20 ou superior.
29
+
30
+ ## Uso
31
+
32
+ ```bash
33
+ # Apura CBS e IBS de uma nota — público, sem chave
34
+ dualisia apurar nota.xml --ano 2027
35
+
36
+ # Projeta a carga ano a ano da transição
37
+ dualisia simular nota.xml --anos 2026,2029,2033
38
+
39
+ # Dados do escritório exige API key
40
+ export DUALISIA_API_KEY=dlai_live_xxx
41
+ dualisia clientes --limit 10
42
+ dualisia documentos --from 2026-01-01 --to 2026-12-31
43
+ ```
44
+
45
+ Saída de `apurar`:
46
+
47
+ ```
48
+ Nota 35260612345678000190550010000001231234567890
49
+ Emitente Empresa Teste Ltda (SP)
50
+ Valor total R$ 2.000,00
51
+ Itens 2
52
+
53
+ Ano de referência 2027 · modo projecao
54
+ Regime atual R$ 545,00 (27.25%)
55
+ Regime novo R$ 176,00 (8.8%)
56
+
57
+ Atenção: este ano usa alíquotas ESTIMADAS — dependem de Resolução do Senado
58
+ ainda não publicada.
59
+ ```
60
+
61
+ `--json` devolve a resposta bruta da API, para encadear com `jq`:
62
+
63
+ ```bash
64
+ dualisia apurar nota.xml --ano 2027 --json | jq '.resultado.regimeNovo'
65
+ ```
66
+
67
+ ## Comandos
68
+
69
+ | Comando | O que faz | Autenticação |
70
+ | --- | --- | --- |
71
+ | `apurar <arquivo.xml>` | Apura CBS e IBS de uma NF-e (modelo 55 ou 65). | pública |
72
+ | `simular <arquivo.xml>` | Projeta a carga ano a ano, 2026-2033. | pública |
73
+ | `clientes` | Lista os clientes PJ do escritório. | API key |
74
+ | `documentos` | Lista as NF-e já processadas. | API key |
75
+
76
+ A chave é gerada pelo owner do escritório no painel do Dualisia, em
77
+ Configurações → API keys.
78
+
79
+ ## Variáveis de ambiente
80
+
81
+ | Variável | Para que serve |
82
+ | --- | --- |
83
+ | `DUALISIA_API_KEY` | API key da organização (`dlai_live_...`). Equivale a `--api-key`. |
84
+ | `DUALISIA_BASE_URL` | Origem da API. Padrão: `https://dualisia.com.br`. Equivale a `--base-url`. |
85
+
86
+ ## Códigos de saída
87
+
88
+ | Código | Significado |
89
+ | --- | --- |
90
+ | `0` | Sucesso. |
91
+ | `1` | Erro de uso ou erro 4xx da API. |
92
+ | `2` | Erro 5xx da API. |
93
+
94
+ Erros da API chegam em JSON estruturado, com um campo `code` estável para
95
+ ramificar e um `hint` dizendo o que corrigir. O CLI imprime os dois.
96
+
97
+ ## O que este CLI não é
98
+
99
+ O cálculo acontece no servidor: este pacote é um cliente da API do Dualisia,
100
+ não um motor fiscal offline. Ele **não** transmite obrigação acessória à Receita
101
+ ou à SEFAZ, não baixa XML da SEFAZ e não emite parecer fiscal.
102
+
103
+ As alíquotas de 2026 a 2028 são as publicadas na LC 214/2025. De 2029 em diante
104
+ dependem de Resolução do Senado ainda não publicada, e por isso são tratadas
105
+ como estimativas — a saída sempre diz quando é o caso.
106
+
107
+ ## Desenvolvimento
108
+
109
+ ```bash
110
+ git clone https://github.com/munhoz-iago/dualisia-cli.git
111
+ cd dualisia-cli
112
+ npm install
113
+ npm test
114
+ node ./bin/dualisia.mjs --help
115
+ ```
116
+
117
+ Não há etapa de build: os arquivos são publicados como estão.
118
+
119
+ ### Publicação
120
+
121
+ ```bash
122
+ npm version patch
123
+ npm publish --otp=123456
124
+ ```
125
+
126
+ O npm exige segundo fator para publicar. O `.npmrc` do repositório fixa o
127
+ registry em HTTPS, para não depender da configuração da máquina.
128
+
129
+ ## Documentação da API
130
+
131
+ - Portal do desenvolvedor: <https://dualisia.com.br/developers>
132
+ - Especificação OpenAPI 3.1: <https://dualisia.com.br/openapi.json>
133
+
134
+ ## Licença
135
+
136
+ MIT — ver [LICENSE](./LICENSE).
package/bin/dualisia.mjs CHANGED
@@ -1,155 +1,163 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Binário do CLI. Só faz I/O: lê argv e variáveis de ambiente, chama o núcleo
4
- * em `src/index.mjs`, escreve em stdout/stderr e define o código de saída.
5
- *
6
- * Toda a lógica testável vive no núcleo — este arquivo não tem regra de
7
- * negócio de propósito.
8
- */
9
- import { readFile } from "node:fs/promises";
10
- import { basename } from "node:path";
11
- import { createRequire } from "node:module";
12
-
13
- import {
14
- CliError,
15
- COMMANDS,
16
- USAGE,
17
- apurar,
18
- formatApuracao,
19
- formatLista,
20
- listarClientes,
21
- listarDocumentos,
22
- parseArgs,
23
- simular,
24
- } from "../src/index.mjs";
25
-
26
- const require = createRequire(import.meta.url);
27
- const { version } = require("../package.json");
28
-
29
- function fail(message, hint) {
30
- process.stderr.write(`erro: ${message}\n`);
31
- if (hint) process.stderr.write(`${hint}\n`);
32
- }
33
-
34
- async function readXml(path) {
35
- if (!path) {
36
- throw new CliError("Informe o caminho do arquivo XML.", {
37
- hint: "Exemplo: dualisia apurar nota.xml",
38
- });
39
- }
40
- try {
41
- const buffer = await readFile(path);
42
- return { file: new Blob([buffer]), filename: basename(path) };
43
- } catch {
44
- throw new CliError(`Não foi possível ler o arquivo: ${path}`, {
45
- hint: "Confira o caminho e a permissão de leitura.",
46
- });
47
- }
48
- }
49
-
50
- async function main(argv) {
51
- const { command, positionals, flags } = parseArgs(argv);
52
-
53
- if (flags.version) {
54
- process.stdout.write(`${version}\n`);
55
- return 0;
56
- }
57
-
58
- if (!command || flags.help) {
59
- process.stdout.write(USAGE);
60
- return command ? 0 : 1;
61
- }
62
-
63
- const spec = COMMANDS[command];
64
- if (!spec) {
65
- fail(
66
- `Comando desconhecido: ${command}`,
67
- "Rode `dualisia --help` para ver os comandos disponíveis.",
68
- );
69
- return 1;
70
- }
71
-
72
- const apiKey = flags["api-key"] ?? process.env.DUALISIA_API_KEY ?? null;
73
- if (spec.needsKey && !apiKey) {
74
- fail(
75
- `O comando \`${command}\` exige uma API key.`,
76
- "Defina DUALISIA_API_KEY ou passe --api-key. Gere a chave em /dashboard/settings/api-keys.",
77
- );
78
- return 1;
79
- }
80
-
81
- const baseUrl = flags["base-url"] ?? process.env.DUALISIA_BASE_URL;
82
- const raw = flags.json === true;
83
-
84
- let payload;
85
-
86
- switch (command) {
87
- case "apurar": {
88
- const { file, filename } = await readXml(positionals[0]);
89
- payload = await apurar({ baseUrl, file, filename, ano: flags.ano });
90
- process.stdout.write(
91
- (raw ? JSON.stringify(payload, null, 2) : formatApuracao(payload)) + "\n",
92
- );
93
- return 0;
94
- }
95
-
96
- case "simular": {
97
- const { file, filename } = await readXml(positionals[0]);
98
- payload = await simular({ baseUrl, file, filename, anos: flags.anos });
99
- process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
100
- return 0;
101
- }
102
-
103
- case "clientes": {
104
- payload = await listarClientes({
105
- baseUrl,
106
- apiKey,
107
- limit: flags.limit,
108
- offset: flags.offset,
109
- });
110
- process.stdout.write(
111
- (raw
112
- ? JSON.stringify(payload, null, 2)
113
- : formatLista(payload, ["cnpj", "razao_social", "uf"])) + "\n",
114
- );
115
- return 0;
116
- }
117
-
118
- case "documentos": {
119
- payload = await listarDocumentos({
120
- baseUrl,
121
- apiKey,
122
- clientId: flags["client-id"],
123
- from: flags.from,
124
- to: flags.to,
125
- limit: flags.limit,
126
- offset: flags.offset,
127
- });
128
- process.stdout.write(
129
- (raw
130
- ? JSON.stringify(payload, null, 2)
131
- : formatLista(payload, [
132
- "chave_acesso",
133
- "data_emissao",
134
- "valor_total",
135
- ])) + "\n",
136
- );
137
- return 0;
138
- }
139
-
140
- default:
141
- return 1;
142
- }
143
- }
144
-
145
- try {
146
- process.exitCode = await main(process.argv.slice(2));
147
- } catch (error) {
148
- if (error instanceof CliError) {
149
- fail(error.message, error.hint);
150
- process.exitCode = error.exitCode;
151
- } else {
152
- fail(error?.message ?? String(error));
153
- process.exitCode = 1;
154
- }
155
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Binário do CLI. Só faz I/O: lê argv e variáveis de ambiente, chama o núcleo
4
+ * em `src/index.mjs`, escreve em stdout/stderr e define o código de saída.
5
+ *
6
+ * Toda a lógica testável vive no núcleo — este arquivo não tem regra de
7
+ * negócio de propósito.
8
+ */
9
+ import { readFile } from "node:fs/promises";
10
+ import { basename } from "node:path";
11
+ import { createRequire } from "node:module";
12
+
13
+ import {
14
+ CliError,
15
+ COMMANDS,
16
+ USAGE,
17
+ apurar,
18
+ formatApuracao,
19
+ formatLista,
20
+ listarClientes,
21
+ listarDocumentos,
22
+ parseArgs,
23
+ simular,
24
+ } from "../src/index.mjs";
25
+
26
+ const require = createRequire(import.meta.url);
27
+ const { version } = require("../package.json");
28
+
29
+ function fail(message, hint) {
30
+ process.stderr.write(`erro: ${message}\n`);
31
+ if (hint) process.stderr.write(`${hint}\n`);
32
+ }
33
+
34
+ async function readXml(path) {
35
+ if (!path) {
36
+ throw new CliError("Informe o caminho do arquivo XML.", {
37
+ hint: "Exemplo: dualisia apurar nota.xml",
38
+ });
39
+ }
40
+ try {
41
+ const buffer = await readFile(path);
42
+ return { file: new Blob([buffer]), filename: basename(path) };
43
+ } catch {
44
+ throw new CliError(`Não foi possível ler o arquivo: ${path}`, {
45
+ hint: "Confira o caminho e a permissão de leitura.",
46
+ });
47
+ }
48
+ }
49
+
50
+ async function main(argv) {
51
+ const { command, positionals, flags } = parseArgs(argv);
52
+
53
+ if (flags.version) {
54
+ process.stdout.write(`${version}\n`);
55
+ return 0;
56
+ }
57
+
58
+ // Ajuda pedida explicitamente é sucesso: vai pro stdout e sai 0, senão
59
+ // `dualisia --help && algo` quebra e o texto polui um pipe de dados.
60
+ if (flags.help) {
61
+ process.stdout.write(USAGE);
62
+ return 0;
63
+ }
64
+
65
+ // Invocação sem comando é erro de uso: o texto vai pro stderr e o código é 1.
66
+ if (!command) {
67
+ process.stderr.write(USAGE);
68
+ return 1;
69
+ }
70
+
71
+ const spec = COMMANDS[command];
72
+ if (!spec) {
73
+ fail(
74
+ `Comando desconhecido: ${command}`,
75
+ "Rode `dualisia --help` para ver os comandos disponíveis.",
76
+ );
77
+ return 1;
78
+ }
79
+
80
+ const apiKey = flags["api-key"] ?? process.env.DUALISIA_API_KEY ?? null;
81
+ if (spec.needsKey && !apiKey) {
82
+ fail(
83
+ `O comando \`${command}\` exige uma API key.`,
84
+ "Defina DUALISIA_API_KEY ou passe --api-key. Gere a chave em /dashboard/settings/api-keys.",
85
+ );
86
+ return 1;
87
+ }
88
+
89
+ const baseUrl = flags["base-url"] ?? process.env.DUALISIA_BASE_URL;
90
+ const raw = flags.json === true;
91
+
92
+ let payload;
93
+
94
+ switch (command) {
95
+ case "apurar": {
96
+ const { file, filename } = await readXml(positionals[0]);
97
+ payload = await apurar({ baseUrl, file, filename, ano: flags.ano });
98
+ process.stdout.write(
99
+ (raw ? JSON.stringify(payload, null, 2) : formatApuracao(payload)) + "\n",
100
+ );
101
+ return 0;
102
+ }
103
+
104
+ case "simular": {
105
+ const { file, filename } = await readXml(positionals[0]);
106
+ payload = await simular({ baseUrl, file, filename, anos: flags.anos });
107
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
108
+ return 0;
109
+ }
110
+
111
+ case "clientes": {
112
+ payload = await listarClientes({
113
+ baseUrl,
114
+ apiKey,
115
+ limit: flags.limit,
116
+ offset: flags.offset,
117
+ });
118
+ process.stdout.write(
119
+ (raw
120
+ ? JSON.stringify(payload, null, 2)
121
+ : formatLista(payload, ["cnpj", "razao_social", "uf"])) + "\n",
122
+ );
123
+ return 0;
124
+ }
125
+
126
+ case "documentos": {
127
+ payload = await listarDocumentos({
128
+ baseUrl,
129
+ apiKey,
130
+ clientId: flags["client-id"],
131
+ from: flags.from,
132
+ to: flags.to,
133
+ limit: flags.limit,
134
+ offset: flags.offset,
135
+ });
136
+ process.stdout.write(
137
+ (raw
138
+ ? JSON.stringify(payload, null, 2)
139
+ : formatLista(payload, [
140
+ "chave_acesso",
141
+ "data_emissao",
142
+ "valor_total",
143
+ ])) + "\n",
144
+ );
145
+ return 0;
146
+ }
147
+
148
+ default:
149
+ return 1;
150
+ }
151
+ }
152
+
153
+ try {
154
+ process.exitCode = await main(process.argv.slice(2));
155
+ } catch (error) {
156
+ if (error instanceof CliError) {
157
+ fail(error.message, error.hint);
158
+ process.exitCode = error.exitCode;
159
+ } else {
160
+ fail(error?.message ?? String(error));
161
+ process.exitCode = 1;
162
+ }
163
+ }
package/package.json CHANGED
@@ -1,37 +1,46 @@
1
- {
2
- "name": "dualisia-cli",
3
- "version": "0.1.0",
4
- "description": "CLI oficial do Dualisia: apura CBS e IBS de XMLs de NF-e e consulta os dados do escritório pela API REST.",
5
- "keywords": [
6
- "cbs",
7
- "ibs",
8
- "reforma-tributaria",
9
- "nfe",
10
- "fiscal",
11
- "brasil",
12
- "dualisia"
13
- ],
14
- "homepage": "https://dualisia.com.br/developers",
15
- "bugs": "https://dualisia.com.br/contato",
16
- "license": "MIT",
17
- "author": "Dualisia Tecnologia",
18
- "type": "module",
19
- "bin": {
20
- "dualisia": "bin/dualisia.mjs"
21
- },
22
- "exports": {
23
- ".": "./src/index.mjs"
24
- },
25
- "files": [
26
- "bin",
27
- "src/index.mjs",
28
- "README.md",
29
- "LICENSE"
30
- ],
31
- "engines": {
32
- "node": ">=20"
33
- },
34
- "scripts": {
35
- "smoke": "node ./bin/dualisia.mjs --help"
36
- }
37
- }
1
+ {
2
+ "name": "dualisia-cli",
3
+ "version": "0.1.2",
4
+ "description": "CLI oficial do Dualisia: apura CBS e IBS de XMLs de NF-e e consulta os dados do escritório pela API REST.",
5
+ "keywords": [
6
+ "cbs",
7
+ "ibs",
8
+ "reforma-tributaria",
9
+ "nfe",
10
+ "fiscal",
11
+ "brasil",
12
+ "dualisia"
13
+ ],
14
+ "homepage": "https://dualisia.com.br/developers",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/munhoz-iago/dualisia-cli.git"
18
+ },
19
+ "bugs": "https://github.com/munhoz-iago/dualisia-cli/issues",
20
+ "license": "MIT",
21
+ "author": "Dualisia Tecnologia",
22
+ "type": "module",
23
+ "bin": {
24
+ "dualisia": "bin/dualisia.mjs"
25
+ },
26
+ "exports": {
27
+ ".": "./src/index.mjs"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "src/index.mjs",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "scripts": {
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "smoke": "node ./bin/dualisia.mjs --help"
42
+ },
43
+ "devDependencies": {
44
+ "vitest": "^4.1.5"
45
+ }
46
+ }