@botozap/cli 0.1.3 → 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.
package/README.md CHANGED
@@ -206,3 +206,28 @@ pnpm --filter @botozap/cli test # vitest
206
206
  pnpm --filter @botozap/cli build # tsc -> dist/
207
207
  node packages/cli/dist/index.js --help
208
208
  ```
209
+
210
+ ## Atendimento, CRM e Agenda
211
+
212
+ Comandos: `saved-replies`, `inbox-tools`, `opportunities`, `demands`, `radar`,
213
+ `journeys`, `appointments`, `calendar`, `assignments`, `contact-stages` e
214
+ `contact-fields`. Cada subcomando mostra campos e CAS em `--help`; os campos e
215
+ filtros são lidos de `--input-file arquivo.json`. Use `-o json` para scripts.
216
+ O arquivo mantém listas, objetos, null e timestamps precisos sem escape de shell.
217
+
218
+ ```sh
219
+ botozap saved-replies list --input-file filtros.json -o json
220
+ botozap opportunities create --input-file oportunidade.json -o json
221
+ botozap appointments availability --input-file disponibilidade.json -o json
222
+ ```
223
+
224
+ [Contratos e exemplos dos arquivos JSON](https://github.com/bytecraft-fernando/botozap-js/blob/main/docs/attendance-release.md).
225
+
226
+ IA usa exclusivamente credenciais próprias (BYOK): SDK `client.ai`, CLI `botozap ai`
227
+ e ferramentas MCP `ai_*`. Agentes versionados, provedores, credenciais, conhecimento,
228
+ memória, skills, follow-ups e retornos prometidos, roteadores, casos, alertas,
229
+ avisos, propostas de aprendizado e comerciais, controle de acesso (elegibilidade),
230
+ inferências, promessas do operador, catálogo de modelos, execuções e uso. Scopes
231
+ `agents:read/write`; aprovação exige chave criada por
232
+ owner/admin ainda autorizado. Prévia não envia WhatsApp. Não há carteira, créditos
233
+ ou compra de vagas de IA. [Contratos e exemplos IA](https://github.com/bytecraft-fernando/botozap-js/blob/main/docs/ai.md).
@@ -0,0 +1,58 @@
1
+ import { operation } from "./attendance.js";
2
+ export function registerAgenda(program) {
3
+ const group = program
4
+ .command("appointments")
5
+ .description("Agenda completa; scopes appointments:read/write");
6
+ operation(group, "list", "Filtros: customer_id,contact_id,owner_user_id,service_id,status,from,to,date(UTC),page,per_page", (c, _id, p) => c.appointments.list(p));
7
+ operation(group, "get <id>", "Detalha compromisso e revision", (c, id) => c.appointments.get(id));
8
+ operation(group, "create", "Cria compromisso; idempotency_key opcional no header; title,ends_at,time_zone,owner_user_id,service_id,links CRM e meeting_requested opcionais", (c, _id, p) => {
9
+ const { idempotency_key, ...body } = p;
10
+ return c.appointments.create(body, {
11
+ idempotencyKey: idempotency_key,
12
+ });
13
+ }, ["contact_id", "scheduled_at"]);
14
+ operation(group, "update <id>", "Edita compromisso com expected_revision; legado scheduled_at/note continua aceito", (c, id, p) => c.appointments.update(id, p));
15
+ operation(group, "delete <id>", "Exclui compromisso; Google deve estar cancelado e sincronizado antes", (c, id) => c.appointments.delete(id));
16
+ operation(group, "history <id>", "Histórico paginado (page)", (c, id, p) => c.appointments.history(id, p));
17
+ operation(group, "availability", "Horários livres calculados incluindo jornadas, exceções, buffers e calendários", (c, _id, p) => c.appointments.availability(p), ["customer_id", "owner_user_id", "service_id", "from", "to"]);
18
+ for (const kind of ["services", "schedules", "exceptions"]) {
19
+ const configuration = group
20
+ .command(kind)
21
+ .description(`Configuração de ${kind}`);
22
+ operation(configuration, "list", "customer_id e page", (c, _id, p) => c.appointments[kind].list(p), ["customer_id"]);
23
+ const required = kind === "services"
24
+ ? [
25
+ "customer_id",
26
+ "name",
27
+ "duration_minutes",
28
+ "slot_minutes",
29
+ "buffer_before_minutes",
30
+ "buffer_after_minutes",
31
+ "minimum_notice_minutes",
32
+ "booking_horizon_days",
33
+ "active",
34
+ ]
35
+ : kind === "schedules"
36
+ ? ["customer_id", "owner_user_id", "time_zone", "windows"]
37
+ : [
38
+ "customer_id",
39
+ "owner_user_id",
40
+ "local_date",
41
+ "start_minute",
42
+ "end_minute",
43
+ "kind",
44
+ ];
45
+ operation(configuration, "create", "Cria configuração completa; consulte tipos públicos do SDK", (c, _id, p) => kind === "services"
46
+ ? c.appointments.services.create(p)
47
+ : kind === "schedules"
48
+ ? c.appointments.schedules.create(p)
49
+ : c.appointments.exceptions.create(p), required);
50
+ operation(configuration, "update <id>", "Altera campos da configuração com expected_revision", (c, id, p) => kind === "services"
51
+ ? c.appointments.services.update(id, p)
52
+ : kind === "schedules"
53
+ ? c.appointments.schedules.update(id, p)
54
+ : c.appointments.exceptions.update(id, p), ["expected_revision"]);
55
+ if (kind === "exceptions")
56
+ operation(configuration, "delete <id>", "Exclui exceção com expected_revision", (c, id, p) => c.appointments.exceptions.delete(id, Number(p.expected_revision)), ["expected_revision"]);
57
+ }
58
+ }
@@ -0,0 +1,73 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { basename } from "node:path";
3
+ import { AI_CONFIGURABLE_PURPOSES, AI_OPERATIONS } from "@botozap/sdk";
4
+ import { operation } from "./attendance.js";
5
+ const kebab = (value) => value.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
6
+ /** Every command maps to one declared /ai endpoint; inputs preserve exact revisions. */
7
+ export function registerAi(program) {
8
+ const root = program
9
+ .command("ai")
10
+ .description("IA BYOK: agentes, bibliotecas, roteamento e operação; agents:read/write");
11
+ operation(root, "conversation-control <id>", "Pausa/retoma agente na conversa; resume pode gerar respostas.", (client, id, input) => client.conversations.controlAgent(id, input.action), ["action"]);
12
+ for (const group of new Set(AI_OPERATIONS.map((o) => o.group))) {
13
+ const command = root
14
+ .command(kebab(group))
15
+ .description(`Operações de ${group}`);
16
+ for (const op of AI_OPERATIONS.filter((o) => o.group === group)) {
17
+ const required = Object.entries(op.fields)
18
+ .filter(([, v]) => !v.optional)
19
+ .map(([k]) => k);
20
+ const fields = op.shape === "multipart"
21
+ ? required
22
+ .filter((k) => k !== "file" && k !== "file_name")
23
+ .concat("file_path")
24
+ : required;
25
+ operation(command, kebab(op.name), `${op.method} ${op.path}. ${op.description} Campos: ${Object.keys(op.fields)
26
+ .filter((k) => k !== "file")
27
+ .join(", ")}${op.shape === "multipart" ? ", file_path (arquivo local)" : ""}.`, async (client, _id, raw) => {
28
+ const input = { ...raw };
29
+ if (op.shape === "multipart") {
30
+ const path = String(input.file_path);
31
+ const limit = op.group === "skills" ? 5 * 1024 * 1024 : 20 * 1024 * 1024;
32
+ const size = (await stat(path)).size;
33
+ if (!size || size > limit)
34
+ throw new Error("Arquivo vazio ou acima do limite de upload.");
35
+ const bytes = await readFile(path);
36
+ if (!bytes.length || bytes.length > limit)
37
+ throw new Error("Arquivo vazio ou acima do limite de upload.");
38
+ delete input.file_path;
39
+ input.file = new Blob([bytes], {
40
+ type: typeof input.mime_type === "string"
41
+ ? input.mime_type
42
+ : /\.pdf$/i.test(String(input.file_name ?? path))
43
+ ? "application/pdf"
44
+ : "application/octet-stream",
45
+ });
46
+ delete input.mime_type;
47
+ input.file_name ??= basename(path);
48
+ }
49
+ return client.ai.invoke(group, op.name, input);
50
+ }, fields, {
51
+ maxBytes: 1024 * 1024,
52
+ validate(input) {
53
+ for (const [name, field] of Object.entries(op.fields)) {
54
+ const value = input[name];
55
+ if (value === undefined)
56
+ continue;
57
+ if (field.type === "configurablePurpose" &&
58
+ !AI_CONFIGURABLE_PURPOSES.includes(String(value)))
59
+ throw new Error("Esta finalidade é configurada na versão do agente ou no roteador.");
60
+ if ((field.type === "revision" || field.type === "revisionZero") &&
61
+ (typeof value !== "string" || !/^\d+$/.test(value)))
62
+ throw new Error(`${name} deve ser a string de revisão recebida da API.`);
63
+ if (field.type === "number" &&
64
+ (typeof value !== "number" || !Number.isFinite(value)))
65
+ throw new Error(`${name} deve ser número.`);
66
+ if (field.type === "true" && value !== true)
67
+ throw new Error(`${name} exige confirmação true.`);
68
+ }
69
+ },
70
+ });
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,139 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { context } from "./shared.js";
3
+ import { printJson, printDetail, printTable, printLine } from "../output.js";
4
+ /** JSON files preserve nested fields, null, Unicode and microsecond CAS timestamps. */
5
+ export async function readInput(path, maxBytes = 256 * 1024) {
6
+ if (!path)
7
+ return {};
8
+ const raw = await readFile(path, "utf8");
9
+ if (Buffer.byteLength(raw) > maxBytes)
10
+ throw new Error(`JSON excede ${maxBytes} bytes.`);
11
+ const value = JSON.parse(raw);
12
+ if (!value || typeof value !== "object" || Array.isArray(value))
13
+ throw new Error("O arquivo deve conter um objeto JSON.");
14
+ return value;
15
+ }
16
+ function display(value, format) {
17
+ if (format === "json")
18
+ return printJson(value ?? { success: true });
19
+ if (value === undefined)
20
+ return printLine("Operação concluída.");
21
+ if (Array.isArray(value))
22
+ return printTable(value, [
23
+ { header: "ID", key: "id" },
24
+ { header: "NOME", key: "name" },
25
+ { header: "TÍTULO", key: "title" },
26
+ ]);
27
+ if (value && typeof value === "object") {
28
+ const row = value;
29
+ if (Array.isArray(row.data)) {
30
+ printTable(row.data, [
31
+ { header: "ID", key: "id" },
32
+ { header: "NOME", key: "name" },
33
+ { header: "TÍTULO", key: "title" },
34
+ { header: "ESTADO", key: "status" },
35
+ ]);
36
+ if (row.meta)
37
+ printDetail(row.meta);
38
+ if (row.paging)
39
+ printDetail(row.paging);
40
+ return;
41
+ }
42
+ return printDetail(row);
43
+ }
44
+ printJson(value);
45
+ }
46
+ export function operation(parent, signature, description, run, required = [], inputOptions) {
47
+ const cmd = parent
48
+ .command(signature)
49
+ .description(description)
50
+ .option("--input-file <path>", "arquivo JSON com campos/filtros da operação; consulte --help");
51
+ if (required.length)
52
+ cmd.addHelpText("after", `\nCampos obrigatórios no JSON: ${required.join(", ")}.`);
53
+ cmd.action(async (...args) => {
54
+ const command = args.at(-1);
55
+ const opts = command.opts();
56
+ const data = await readInput(opts.inputFile, inputOptions?.maxBytes);
57
+ for (const key of required)
58
+ if (data[key] === undefined)
59
+ throw new Error(`Campo obrigatório no JSON: ${key}.`);
60
+ if (inputOptions)
61
+ inputOptions.validate(data);
62
+ else
63
+ for (const key of ["expected_version", "version", "expected_revision"])
64
+ if (data[key] !== undefined &&
65
+ (!Number.isInteger(data[key]) ||
66
+ Number(data[key]) < (signature.startsWith("stage-rule") ? 0 : 1)))
67
+ throw new Error(`${key} deve ser inteiro positivo.`);
68
+ const { client, format } = context(command);
69
+ const id = signature.includes("<id>") ? String(args[0]) : undefined;
70
+ display(await run(client, id, data), format);
71
+ });
72
+ return cmd;
73
+ }
74
+ export function registerAttendance(program) {
75
+ const saved = program
76
+ .command("saved-replies")
77
+ .description("Respostas compartilhadas (scope saved_replies:read/write); pessoais pertencem à sessão");
78
+ operation(saved, "list", "Filtros: query, customer_id (UUID ou account), include_account, page, per_page", (c, _id, p) => c.savedReplies.list(p));
79
+ operation(saved, "get <id>", "Lê resposta e updated_at para edição", (c, id) => c.savedReplies.get(id));
80
+ operation(saved, "create", "Cria resposta: title, body, shortcut, customer_id", (c, _id, p) => c.savedReplies.create(p), ["title", "body"]);
81
+ operation(saved, "update <id>", "Atualiza campos com CAS (expected_updated_at exato, sem arredondar)", (c, id, p) => c.savedReplies.update(id, p), ["expected_updated_at"]);
82
+ operation(saved, "delete <id>", "Exclui resposta com CAS", (c, id, p) => c.savedReplies.delete(id, p), ["expected_updated_at"]);
83
+ const inbox = program
84
+ .command("inbox-tools")
85
+ .description("Notas, retornos, arquivo e adiamento; scopes inbox:read/write");
86
+ operation(inbox, "get <id>", "Ferramentas da conversa; page/per_page para notas e retornos", (c, id, p) => c.inbox.get(id, p));
87
+ operation(inbox, "mutate <id>", "operation: archive, unarchive, snooze, unsnooze, note_create/update/delete, reminder_create/update; CAS expected_version nas alterações", (c, id, p) => c.inbox.mutate(id, p), ["operation"]);
88
+ for (const resource of ["opportunities", "demands"]) {
89
+ const group = program
90
+ .command(resource)
91
+ .description(`${resource === "opportunities" ? "Oportunidades" : "Demandas"} do CRM; scopes crm:read/write`);
92
+ operation(group, "list", "customer_id obrigatório; q,status,contact_id,owner_user_id,stage_id,page,per_page", (c, _id, p) => c[resource].list(p), ["customer_id"]);
93
+ operation(group, "get <id>", "Lê registro e versão", (c, id) => c[resource].get(id));
94
+ operation(group, "create", "Cria registro completo; campos conforme SDK", (c, _id, p) => resource === "opportunities"
95
+ ? c.opportunities.create(p)
96
+ : c.demands.create(p), ["customer_id", "contact_id", "title"]);
97
+ operation(group, "update <id>", "Altera campos, responsável, estado e próximo passo com CAS", (c, id, p) => c[resource].update(id, p), ["expected_version"]);
98
+ operation(group, "activities <id>", "Histórico paginado (page)", (c, id, p) => c[resource].activities(id, p));
99
+ operation(group, "conversations <id>", "Conversas vinculadas (page)", (c, id, p) => c[resource].conversations(id, p));
100
+ operation(group, "link-conversation <id>", "Vincula conversa do mesmo contato", (c, id, p) => c[resource].linkConversation(id, String(p.conversation_id)), ["conversation_id"]);
101
+ operation(group, "unlink-conversation <id>", "Remove vínculo com conversa", (c, id, p) => c[resource].unlinkConversation(id, String(p.conversation_id)), ["conversation_id"]);
102
+ }
103
+ const radar = program
104
+ .command("radar")
105
+ .description("Pendências do CRM; scopes crm:read/write");
106
+ operation(radar, "list", "customer_id; filtros bucket,entity_type,owner_user_id,reason,page,per_page", (c, _id, p) => c.radar.list(p), ["customer_id"]);
107
+ operation(radar, "stage-rules", "Critérios das etapas do Cliente", (c, _id, p) => c.radar.stageRules(String(p.customer_id)), ["customer_id"]);
108
+ operation(radar, "stage-rule <id>", "Configura critério: expected_version=0 cria; cold_hours,critical_hours,require_owner,require_next_step", (c, id, p) => c.radar.configureStageRule(id, p), [
109
+ "customer_id",
110
+ "expected_version",
111
+ "cold_hours",
112
+ "critical_hours",
113
+ "require_owner",
114
+ "require_next_step",
115
+ ]);
116
+ const journey = program
117
+ .command("journeys")
118
+ .description("Réguas e execuções; scopes journeys:read/write; ambiente live");
119
+ operation(journey, "list", "Filtros customer_id,limit,after", (c, _id, p) => c.journeys.list(p));
120
+ operation(journey, "get <id>", "Definição e passos da régua", (c, id, p) => c.journeys.get(id, p));
121
+ operation(journey, "create", "Configuração completa da régua; steps é lista de template_id,delay_minutes,variable_map", (c, _id, p) => c.journeys.create(p), ["customer_id", "name", "steps"]);
122
+ operation(journey, "update <id>", "Substitui configuração completa com CAS version", (c, id, p) => c.journeys.update(id, p), ["customer_id", "name", "steps", "version"]);
123
+ operation(journey, "delete <id>", "Arquiva régua com CAS version (preserva histórico)", (c, id, p) => c.journeys.delete(id, Number(p.version)), ["version"]);
124
+ operation(journey, "control <id>", "action: pause/resume/archive e version", (c, id, p) => c.journeys.control(id, p), ["action", "version"]);
125
+ operation(journey, "runs <id>", "Lista execuções da régua; customer_id,limit,after", (c, id, p) => c.journeys.runs(id, p));
126
+ operation(journey, "enroll <id>", "Programa ocorrência única para contato (pode gerar envio automático)", (c, id, p) => c.journeys.enroll(id, p), ["contact_id", "occurrence_key"]);
127
+ operation(journey, "get-run <id>", "Execução com passos, falhas e comprovantes", (c, id) => c.journeys.getRun(id));
128
+ operation(journey, "control-run <id>", "action: stop ou acknowledge (com note)", (c, id, p) => c.journeys.controlRun(id, p), ["action"]);
129
+ const assignments = program
130
+ .command("assignments")
131
+ .description("Atribuições de conversas a membros da Conta");
132
+ operation(assignments, "list <id>", "Atribuições da conversa; page,per_page", (c, id, p) => c.conversations.listAssignments(id, p));
133
+ operation(assignments, "create <id>", "Atribui conversa a user_id; notes opcional", (c, id, p) => c.conversations.createAssignment(id, p), ["user_id"]);
134
+ operation(assignments, "get <id>", "Lê assignment_id da conversa", (c, id, p) => c.conversations.getAssignment(id, String(p.assignment_id)), ["assignment_id"]);
135
+ operation(assignments, "update <id>", "Atualiza atribuição: assignment_id + campos permitidos pela API", (c, id, p) => {
136
+ const { assignment_id, ...patch } = p;
137
+ return c.conversations.updateAssignment(id, String(assignment_id), patch);
138
+ }, ["assignment_id"]);
139
+ }
@@ -0,0 +1,14 @@
1
+ import { operation } from "./attendance.js";
2
+ export function registerCalendar(program) {
3
+ const group = program
4
+ .command("calendar")
5
+ .description("Google Calendar: autorize primeiro em /calendarios no Painel; scopes calendar:read/write");
6
+ operation(group, "connections", "Lista conexões (page,per_page)", (c, _id, p) => c.calendar.connections(p));
7
+ operation(group, "disconnect <id>", "Desconecta conta Google e seus calendários", (c, id) => c.calendar.disconnect(id));
8
+ operation(group, "calendars <id>", "Lista calendários da conexão", (c, id) => c.calendar.calendars(id));
9
+ operation(group, "refresh <id>", "Atualiza catálogo de calendários Google da conexão", (c, id) => c.calendar.refresh(id));
10
+ operation(group, "select <id>", "Configura destino/ocupação com CAS expected_revision", (c, id, p) => c.calendar.select(id, p), ["destination", "include_busy", "expected_revision"]);
11
+ operation(group, "jobs", "Lista sincronizações (connection_id,page,per_page)", (c, _id, p) => c.calendar.jobs(p));
12
+ operation(group, "retry-job <id>", "Reagenda sincronização com falha", (c, id) => c.calendar.retryJob(id));
13
+ operation(group, "resolve-conflict <id>", "ID do compromisso; choice local/remote e expected_revision", (c, id, p) => c.calendar.resolveConflict(id, p), ["choice", "expected_revision"]);
14
+ }
@@ -0,0 +1,18 @@
1
+ import { operation } from "./attendance.js";
2
+ export function registerContactConfiguration(program) {
3
+ const stages = program
4
+ .command("contact-stages")
5
+ .description("Etapas do funil por Cliente; contacts:read/write");
6
+ operation(stages, "list", "customer_id recomendado; omitir só em Conta com exatamente um Cliente", (c, _id, p) => c.contactStages.list(p));
7
+ operation(stages, "create", "label e color (zinc,green,amber,pink); customer_id recomendado", (c, _id, p) => c.contactStages.create(p), ["label", "color"]);
8
+ operation(stages, "update <id>", "Altera label,color,position; customer_id recomendado", (c, id, p) => c.contactStages.update(id, p));
9
+ operation(stages, "delete <id>", "Exclui etapa do Cliente; customer_id recomendado", (c, id, p) => c.contactStages.delete(id, p));
10
+ operation(stages, "reorder", "stage_ids deve conter todas as etapas exatamente uma vez; customer_id recomendado", (c, _id, p) => c.contactStages.reorder(p), ["stage_ids"]);
11
+ const fields = program
12
+ .command("contact-fields")
13
+ .description("Campos personalizados da Conta; contacts:read/write");
14
+ operation(fields, "list", "Lista definições e tipos de campos disponíveis", (c) => c.contactFields.list());
15
+ operation(fields, "create", "label; type text/number/boolean/date e key opcionais", (c, _id, p) => c.contactFields.create(p), ["label"]);
16
+ operation(fields, "update <id>", "Altera label e position; tipo e chave são imutáveis", (c, id, p) => c.contactFields.update(id, p));
17
+ operation(fields, "delete <id>", "Exclui definição do campo", (c, id) => c.contactFields.delete(id));
18
+ }
@@ -1,5 +1,8 @@
1
1
  import { context, toInt, toBool } from "./shared.js";
2
2
  import { printJson, printTable, printDetail, printCursorFooter, printLine, } from "../output.js";
3
+ function collect(value, previous) {
4
+ return [...(previous ?? []), value];
5
+ }
3
6
  export function registerContacts(program) {
4
7
  const contacts = program
5
8
  .command("contacts")
@@ -60,6 +63,7 @@ export function registerContacts(program) {
60
63
  .option("--username <username>", "username")
61
64
  .option("--customer-id <id>", "cliente vinculado")
62
65
  .option("--phone-number-id <id>", "número vinculado")
66
+ .option("--tag <tag>", "tag do contato (repetível; até 20 × 40 caracteres)", collect)
63
67
  .action(async (opts, cmd) => {
64
68
  const { client, format } = context(cmd);
65
69
  const data = await client.contacts.create({
@@ -70,6 +74,7 @@ export function registerContacts(program) {
70
74
  username: opts.username,
71
75
  customer_id: opts.customerId,
72
76
  phone_number_id: opts.phoneNumberId,
77
+ ...(opts.tag ? { tags: opts.tag } : {}),
73
78
  });
74
79
  if (format === "json")
75
80
  return printJson(data);
@@ -81,6 +86,9 @@ export function registerContacts(program) {
81
86
  .description("Atualiza um contato")
82
87
  .option("--profile-name <nome>", "novo nome do perfil")
83
88
  .option("--username <username>", "novo username")
89
+ .option("--tag <tag>", "substitui todas as tags (repetível)", collect)
90
+ .option("--add-tag <tag>", "acrescenta tag (repetível)", collect)
91
+ .option("--remove-tag <tag>", "remove tag (repetível)", collect)
84
92
  .action(async (id, opts, cmd) => {
85
93
  const { client, format } = context(cmd);
86
94
  const body = {};
@@ -88,8 +96,17 @@ export function registerContacts(program) {
88
96
  body.profile_name = opts.profileName;
89
97
  if (opts.username !== undefined)
90
98
  body.username = opts.username;
99
+ if (opts.tag)
100
+ body.tags = opts.tag;
101
+ if (opts.addTag)
102
+ body.add_tags = opts.addTag;
103
+ if (opts.removeTag)
104
+ body.remove_tags = opts.removeTag;
105
+ if (opts.tag && (opts.addTag || opts.removeTag)) {
106
+ throw new Error("Use --tag para substituir a lista ou --add-tag/--remove-tag para alterá-la, não os dois.");
107
+ }
91
108
  if (Object.keys(body).length === 0) {
92
- throw new Error("Informe ao menos --profile-name ou --username.");
109
+ throw new Error("Informe ao menos --profile-name, --username, --tag, --add-tag ou --remove-tag.");
93
110
  }
94
111
  const data = await client.contacts.update(id, body);
95
112
  if (format === "json")
package/dist/index.js CHANGED
@@ -4,6 +4,11 @@ import { Command } from "commander";
4
4
  import pc from "picocolors";
5
5
  import { resolveFormat } from "./output.js";
6
6
  import { renderErrorText, renderErrorJson } from "./render-error.js";
7
+ import { registerContactConfiguration } from "./commands/contact-configuration.js";
8
+ import { registerCalendar } from "./commands/calendar.js";
9
+ import { registerAi } from "./commands/ai.js";
10
+ import { registerAgenda } from "./commands/agenda.js";
11
+ import { registerAttendance } from "./commands/attendance.js";
7
12
  import { registerMessages } from "./commands/messages.js";
8
13
  import { registerConversations } from "./commands/conversations.js";
9
14
  import { registerContacts } from "./commands/contacts.js";
@@ -38,6 +43,11 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
38
43
  selectedFormat = resolveFormat(actionCommand.optsWithGlobals().output);
39
44
  });
40
45
  // Recursos
46
+ registerAttendance(program);
47
+ registerAgenda(program);
48
+ registerAi(program);
49
+ registerCalendar(program);
50
+ registerContactConfiguration(program);
41
51
  registerMessages(program);
42
52
  registerConversations(program);
43
53
  registerContacts(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botozap/cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "CLI dev-first para a API pública do BotoZap (WhatsApp Cloud API multi-tenant).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,22 +27,21 @@
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "scripts": {
31
- "build": "tsc -p tsconfig.json",
32
- "dev": "tsc -p tsconfig.json --watch",
33
- "typecheck": "tsc -p tsconfig.json --noEmit",
34
- "test": "vitest run",
35
- "prepublishOnly": "pnpm run build",
36
- "start": "node dist/index.js"
37
- },
38
30
  "dependencies": {
39
- "@botozap/sdk": "workspace:*",
40
31
  "commander": "^12.1.0",
41
- "picocolors": "^1.1.1"
32
+ "picocolors": "^1.1.1",
33
+ "@botozap/sdk": "0.4.0"
42
34
  },
43
35
  "devDependencies": {
44
36
  "@types/node": "^20.19.0",
45
37
  "typescript": "^5.6.0",
46
38
  "vitest": "^3.0.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "dev": "tsc -p tsconfig.json --watch",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit",
44
+ "test": "vitest run",
45
+ "start": "node dist/index.js"
47
46
  }
48
- }
47
+ }