@praeko/cli 0.0.0-stage → 0.1.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.
@@ -0,0 +1,1125 @@
1
+ import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
2
+ import {
3
+ validarPedido
4
+ } from "./chunk-Z325UM27.mjs";
5
+ import {
6
+ __export
7
+ } from "./chunk-LCFK6X55.mjs";
8
+
9
+ // ../core/src/adaptadores/remoto.mjs
10
+ var remoto_exports = {};
11
+ __export(remoto_exports, {
12
+ REF_CHAVE: () => REF_CHAVE,
13
+ criar: () => criar,
14
+ itemDoContrato: () => itemDoContrato
15
+ });
16
+
17
+ // ../sdk/src/legacy.mjs
18
+ var enc = (x) => encodeURIComponent(x);
19
+ function fonte(x) {
20
+ if (x && typeof x.read === "function" && typeof x.size === "number") return x;
21
+ if (x && typeof x.ler === "function" && typeof x.tamanho === "number") return { size: x.tamanho, read: (i, n) => x.ler(i, n) };
22
+ if (typeof Blob !== "undefined" && x instanceof Blob) return { size: x.size, read: async (i, n) => new Uint8Array(await x.slice(i, i + n).arrayBuffer()) };
23
+ const u8 = x instanceof Uint8Array ? x : x instanceof ArrayBuffer ? new Uint8Array(x) : null;
24
+ if (!u8) throw new TypeError("fonte inv\xE1lida: use Uint8Array, ArrayBuffer, Blob ou { tamanho, ler(inicio, n) }");
25
+ return { size: u8.length, read: async (i, n) => u8.subarray(i, i + n) };
26
+ }
27
+ function addLegacy(a) {
28
+ const L = (method, path, { corpo, bytes, consulta, repetir, semChave } = {}) => a._req(method, path, { body: corpo, bytes, query: consulta, retry: repetir ?? method === "GET", noKey: semChave, legacy: true });
29
+ a.midia = {
30
+ iniciar: (meta) => L("POST", "/v1/midia", { corpo: meta }),
31
+ enviarParte: (id, n, bytes) => L("PUT", `/v1/midia/${id}/partes/${n}`, { bytes, repetir: true }),
32
+ enviarConteudo: (id, bytes) => L("PUT", `/v1/midia/${id}/conteudo`, { bytes, repetir: true }),
33
+ completar: (id) => L("POST", `/v1/midia/${id}/completar`),
34
+ obter: (id) => L("GET", `/v1/midia/${id}`),
35
+ listar: (tipo) => L("GET", "/v1/midia", { consulta: { tipo } }),
36
+ remover: (id) => L("DELETE", `/v1/midia/${id}`),
37
+ /** Uploads a whole media (legacy shapes: { id, … } with Portuguese fields). Prefer media.upload. */
38
+ enviar: async (origem, { tipo, nome, mime, parte = 16 * 1024 * 1024, aoProgredir } = {}) => {
39
+ const s = fonte(origem);
40
+ if (!tipo || !nome || !mime) throw new Error("midia.enviar: informe tipo, nome e mime");
41
+ if (s.size <= 32 * 1024 * 1024) {
42
+ const m2 = await a.midia.iniciar({ tipo, nome, mime, tamanho: s.size, simples: true });
43
+ const r = await a.midia.enviarConteudo(m2.id, await s.read(0, s.size));
44
+ aoProgredir?.(s.size, s.size);
45
+ return r;
46
+ }
47
+ const m = await a.midia.iniciar({ tipo, nome, mime, tamanho: s.size });
48
+ const tam = Math.max(m.parteMinima || 0, Math.min(parte, m.parteMaxima || parte));
49
+ for (let n = 1, pos = 0; pos < s.size; n++, pos += tam) {
50
+ await a.midia.enviarParte(m.id, n, await s.read(pos, tam));
51
+ aoProgredir?.(Math.min(pos + tam, s.size), s.size);
52
+ }
53
+ return a.midia.completar(m.id);
54
+ }
55
+ };
56
+ Object.assign(a.posts, {
57
+ criar: (pedido) => L("POST", "/v1/posts", { corpo: pedido }),
58
+ listar: (filtros = {}) => L("GET", "/v1/posts", { consulta: filtros }),
59
+ obter: (id) => L("GET", `/v1/posts/${enc(id)}`),
60
+ adotar: (corpo) => L("POST", "/v1/posts/adotar", { corpo }),
61
+ agendarYoutube: (id, quando) => L("POST", `/v1/posts/${enc(id)}/youtube/agendar`, { corpo: { quando } }),
62
+ editar: (id, mudancas) => L("PATCH", `/v1/posts/${enc(id)}`, { corpo: mudancas }),
63
+ publicarAgora: (id, { rede } = {}) => L("POST", `/v1/posts/${enc(id)}/publicar-agora`, { consulta: { rede } }),
64
+ cancelar: (id) => L("DELETE", `/v1/posts/${enc(id)}`),
65
+ comentar: (id, itemId) => L("POST", `/v1/posts/${enc(id)}/itens/${itemId}/comentar`)
66
+ });
67
+ a.canais = {
68
+ listar: ({ verificar } = {}) => L("GET", "/v1/canais", { consulta: { verificar: verificar ? 1 : void 0 } }),
69
+ vagas: (rede) => L("GET", `/v1/canais/${rede}/vagas`),
70
+ informarSeguidores: (rede, valor, { dia } = {}) => L("PUT", `/v1/canais/${rede}/seguidores`, { corpo: { valor, ...dia ? { dia } : {} } }),
71
+ seguidores: (rede) => L("GET", `/v1/canais/${rede}/seguidores`),
72
+ definirVagas: (rede, vagas, { fuso } = {}) => L("PUT", `/v1/canais/${rede}/vagas`, { corpo: { vagas, ...fuso ? { fuso } : {} } }),
73
+ conectar: (rede) => L("GET", `/v1/conexoes/${rede}/iniciar`),
74
+ desconectar: (rede) => L("DELETE", `/v1/conexoes/${rede}`),
75
+ enviarTokenInstagram: (corpo) => L("PUT", "/v1/conexoes/instagram/token", { corpo })
76
+ };
77
+ Object.assign(a.instagram, {
78
+ midias: ({ max, pagina } = {}) => L("GET", "/v1/instagram/midias", { consulta: { max, pagina } }),
79
+ comentarios: ({ midia, semResposta, ordem, max } = {}) => L("GET", "/v1/instagram/comentarios", { consulta: { midia, semResposta: semResposta ? 1 : void 0, ordem, max } }),
80
+ responder: (comentarioId, texto) => L("POST", `/v1/instagram/comentarios/${enc(comentarioId)}/responder`, { corpo: { texto } }),
81
+ ocultar: (comentarioId, ocultar = true) => L("POST", `/v1/instagram/comentarios/${enc(comentarioId)}/ocultar`, { corpo: { ocultar } }),
82
+ resolver: (comentarioId) => L("POST", `/v1/instagram/comentarios/${enc(comentarioId)}/resolver`),
83
+ reabrir: (comentarioId) => L("DELETE", `/v1/instagram/comentarios/${enc(comentarioId)}/resolver`)
84
+ });
85
+ a.comunidade = { comentarios: ({ rede, semResposta, ordem, max } = {}) => L("GET", "/v1/comentarios", { consulta: { rede, semResposta: semResposta ? 1 : void 0, ordem, max } }) };
86
+ Object.assign(a.youtube, {
87
+ lerVideo: (id) => L("GET", `/v1/youtube/videos/${enc(id)}`),
88
+ editarVideo: (id, { titulo, descricao, tags, simular } = {}) => L("PATCH", `/v1/youtube/videos/${enc(id)}`, { corpo: { titulo, descricao, tags, simular } }),
89
+ comentarios: ({ video, semResposta, ordem, max, pagina } = {}) => L("GET", "/v1/youtube/comentarios", { consulta: { video, semResposta: semResposta ? 1 : void 0, ordem, max, pagina } }),
90
+ resolver: (threadId) => L("POST", `/v1/youtube/comentarios/${enc(threadId)}/resolver`),
91
+ reabrir: (threadId) => L("DELETE", `/v1/youtube/comentarios/${enc(threadId)}/resolver`),
92
+ responder: (threadId, texto) => L("POST", `/v1/youtube/comentarios/${enc(threadId)}/responder`, { corpo: { texto } })
93
+ });
94
+ a.youtube.videos = ({ max, pagina } = {}) => L("GET", "/v1/youtube/videos", { consulta: { max, pagina } });
95
+ a.insights.obter = ({ de, ate, canal, etiqueta, tipo, comparar, ordem, direcao, pagina, limite } = {}) => L("GET", "/v1/insights", { consulta: { de, ate, canal, etiqueta, tipo, comparar: comparar ? 1 : void 0, ordem, direcao, pagina, limite } });
96
+ a.insights.importar = ({ rede = "tudo", simular } = {}) => L("POST", "/v1/insights/importar", { consulta: { rede, simular: simular ? 1 : void 0 } });
97
+ a.conteudo = { listar: ({ rede, tipo, de, ate, ordem, limite, deslocamento } = {}) => L("GET", "/v1/conteudo", { consulta: { rede, tipo, de, ate, ordem, limite, deslocamento } }) };
98
+ a.mencoes = ({ rede } = {}) => L("GET", "/v1/mencoes", { consulta: { rede } });
99
+ a.chaves = {
100
+ listar: () => L("GET", "/v1/chaves"),
101
+ criar: (nome, escopo = "escrita") => L("POST", "/v1/chaves", { corpo: { nome, escopo } }),
102
+ revogar: (id) => L("DELETE", `/v1/chaves/${id}`)
103
+ };
104
+ a.calendario = ({ de, ate, canal, etiqueta } = {}) => L("GET", "/v1/calendario", { consulta: { de, ate, canal, etiqueta } });
105
+ a.saude = () => L("GET", "/saude", { semChave: true });
106
+ }
107
+
108
+ // ../sdk/src/index.mjs
109
+ var PraekoError = class extends Error {
110
+ constructor(status, code, message, details) {
111
+ super(message);
112
+ this.name = "PraekoError";
113
+ this.status = status;
114
+ this.code = code;
115
+ this.details = details;
116
+ }
117
+ /** @deprecated use `code` */
118
+ get codigo() {
119
+ return this.code;
120
+ }
121
+ /** @deprecated use `details` */
122
+ get detalhes() {
123
+ return this.details;
124
+ }
125
+ /** @deprecated use `message` */
126
+ get mensagem() {
127
+ return this.message;
128
+ }
129
+ };
130
+ var SIMPLE_MAX = 32 * 1024 * 1024;
131
+ var DEFAULT_PART = 16 * 1024 * 1024;
132
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
133
+ function asSource(x) {
134
+ if (x && typeof x.read === "function" && typeof x.size === "number") return x;
135
+ if (x && typeof x.ler === "function" && typeof x.tamanho === "number") return { size: x.tamanho, read: (i, n) => x.ler(i, n) };
136
+ if (typeof Blob !== "undefined" && x instanceof Blob) return { size: x.size, read: async (i, n) => new Uint8Array(await x.slice(i, i + n).arrayBuffer()) };
137
+ const u8 = x instanceof Uint8Array ? x : x instanceof ArrayBuffer ? new Uint8Array(x) : null;
138
+ if (!u8) throw new TypeError("invalid source: use Uint8Array, ArrayBuffer, Blob or { size, read(start, n) }");
139
+ return { size: u8.length, read: async (i, n) => u8.subarray(i, i + n) };
140
+ }
141
+ var ref = (id) => encodeURIComponent(id);
142
+ var idem = (o) => o && o.idempotencyKey ? { idempotencyKey: o.idempotencyKey } : {};
143
+ var uuid = () => globalThis.crypto?.randomUUID ? globalThis.crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}`;
144
+ var Praeko = class {
145
+ // `cookies: true` (browser, same origin): no key; the panel session cookie authenticates.
146
+ /** @param {{ url: string, key?: string, cookies?: boolean, chave?: string, fetch?: typeof fetch, retries?: number, wait?: (ms:number)=>Promise<void> }} o (`chave`, `tentativas`, `espera`: deprecated names) */
147
+ constructor(o = {}) {
148
+ const { url, key = o.chave, cookies = false, fetch: f = globalThis.fetch, retries = o.tentativas ?? 3, wait = o.espera ?? sleep } = o;
149
+ if (!url) throw new Error("Praeko: missing service url (falta a url do servi\xE7o)");
150
+ if (!key && !cookies) throw new Error("Praeko: missing API key praeko_\u2026 (falta a chave)");
151
+ this.url = String(url).replace(/\/+$/, "");
152
+ this._key = key;
153
+ this._cookies = !key && cookies;
154
+ this._fetch = f;
155
+ this._retries = retries;
156
+ this._wait = wait;
157
+ const a = this;
158
+ this.media = {
159
+ create: (meta, o2) => a._req("POST", "/v1/media", { body: meta, ...idem(o2) }),
160
+ uploadPart: (id, n, bytes) => a._req("PUT", `/v1/media/${ref(id)}/parts/${n}`, { bytes, retry: true }),
161
+ uploadContent: (id, bytes) => a._req("PUT", `/v1/media/${ref(id)}/content`, { bytes, retry: true }),
162
+ complete: (id, o2) => a._req("POST", `/v1/media/${ref(id)}/complete`, idem(o2)),
163
+ get: (id) => a._req("GET", `/v1/media/${ref(id)}`),
164
+ list: (type) => a._req("GET", "/v1/media", { query: { type } }),
165
+ remove: (id) => a._req("DELETE", `/v1/media/${ref(id)}`),
166
+ /** Uploads a whole file (one PUT or parts, depending on size). Returns the ready media { id, … }. */
167
+ upload: async (source, { type, name, mime, part = DEFAULT_PART, onProgress } = {}) => {
168
+ const f2 = asSource(source);
169
+ if (!type || !name || !mime) throw new Error("media.upload: provide type, name and mime");
170
+ if (f2.size <= SIMPLE_MAX) {
171
+ const m2 = await a.media.create({ type, name, mime, size: f2.size, simple: true });
172
+ const r = await a.media.uploadContent(m2.id, await f2.read(0, f2.size));
173
+ onProgress?.(f2.size, f2.size);
174
+ return r;
175
+ }
176
+ const m = await a.media.create({ type, name, mime, size: f2.size });
177
+ const size = Math.max(m.minPartSize || 0, Math.min(part, m.maxPartSize || part));
178
+ for (let n = 1, pos = 0; pos < f2.size; n++, pos += size) {
179
+ await a.media.uploadPart(m.id, n, await f2.read(pos, size));
180
+ onProgress?.(Math.min(pos + size, f2.size), f2.size);
181
+ }
182
+ return a.media.complete(m.id);
183
+ }
184
+ };
185
+ this.posts = {
186
+ /** `o.idempotencyKey`: a string (or `true` for a fresh UUID) makes the call safe to retry: the server replays the first response instead of creating a second post (see docs/idempotencia.md). */
187
+ create: (request, o2) => a._req("POST", "/v1/posts", { body: request, ...idem(o2) }),
188
+ list: (filters = {}) => a._req("GET", "/v1/posts", { query: filters }),
189
+ get: (id) => a._req("GET", `/v1/posts/${ref(id)}`),
190
+ /** Registers a Reel already scheduled in Buffer ({ id, title, network: "instagram", scheduledFor, external: { provider: "buffer", id } }); does not call Buffer. */
191
+ adopt: (body, o2) => a._req("POST", "/v1/posts/adopt", { body, ...idem(o2) }),
192
+ scheduleYoutube: (id, scheduledFor, o2) => a._req("POST", `/v1/posts/${ref(id)}/youtube/schedule`, { body: { scheduledFor }, ...idem(o2) }),
193
+ update: (id, changes, o2) => a._req("PATCH", `/v1/posts/${ref(id)}`, { body: changes, ...idem(o2) }),
194
+ publishNow: (id, { network, idempotencyKey } = {}) => a._req("POST", `/v1/posts/${ref(id)}/publish-now`, { query: { network }, ...idem({ idempotencyKey }) }),
195
+ cancel: (id, o2) => a._req("DELETE", `/v1/posts/${ref(id)}`, idem(o2)),
196
+ comment: (id, itemId) => a._req("POST", `/v1/posts/${ref(id)}/items/${ref(itemId)}/comment`)
197
+ };
198
+ this.channels = {
199
+ list: ({ verify } = {}) => a._req("GET", "/v1/channels", { query: { verify: verify ? 1 : void 0 } }),
200
+ /** `channel`: id of one account when the network has several (GET /v1/channels lists them); without it the default channel. */
201
+ slots: (network, { channel } = {}) => a._req("GET", `/v1/channels/${network}/slots`, { query: { channel } }),
202
+ setSlots: (network, slots, { timezone, channel } = {}) => a._req("PUT", `/v1/channels/${network}/slots`, { query: { channel }, body: { slots, ...timezone ? { timezone } : {} } }),
203
+ /** Manual follower count (Instagram only; TikTok comes from the API): PUT { value, day? }; the latest records: GET. */
204
+ setFollowers: (network, value, { day, channel } = {}) => a._req("PUT", `/v1/channels/${network}/followers`, { query: { channel }, body: { value, ...day ? { day } : {} } }),
205
+ followers: (network, { channel } = {}) => a._req("GET", `/v1/channels/${network}/followers`, { query: { channel } }),
206
+ /** `network`: youtube | tiktok | meta (Instagram login). For `meta`, `replace: true` is REQUIRED to replace an existing connection; `instagram` picks the account by username when the login reaches several. */
207
+ connect: (network, { replace, add, instagram, instagramId, probe } = {}) => a._req("GET", `/v1/connections/${network}/start`, { query: { replace: replace ? "true" : void 0, add: add ? "true" : void 0, instagram, instagram_id: instagramId, probe: probe ? "true" : void 0 } }),
208
+ /** Makes one account the default of its network (the one used when a network route has no `channel`). */
209
+ setDefault: (channel) => a._req("PUT", `/v1/channels/${encodeURIComponent(channel)}/default`),
210
+ /** Removes a DISCONNECTED account from the list (history stays). */
211
+ remove: (channel) => a._req("DELETE", `/v1/channels/${encodeURIComponent(channel)}`),
212
+ /** Disconnects ONE account of youtube | tiktok | instagram (Meta token only): revokes the token (best effort) and deletes what the server stored. The channel and its history stay; if it was the default, another connected account takes over. */
213
+ disconnect: (network, { channel } = {}) => a._req("DELETE", `/v1/connections/${network}`, { query: { channel } }),
214
+ /** (admin) Sends the Meta API token for Instagram: { token, userId, username?, type?, host? }. The server validates it with Meta and stores it encrypted. */
215
+ sendInstagramToken: (body) => a._req("PUT", "/v1/connections/instagram/token", { body })
216
+ };
217
+ this.tiktok = { creator: ({ channel } = {}) => a._req("GET", "/v1/tiktok/creator", { query: { channel } }) };
218
+ this.instagram = {
219
+ media: ({ max, page } = {}) => a._req("GET", "/v1/instagram/media", { query: { max, page } }),
220
+ /** `sort`: recent | comments | views. Returns { comments, mediaItems, pending, next }. */
221
+ comments: ({ media, unanswered, sort, max } = {}) => a._req("GET", "/v1/instagram/comments", { query: { media, unanswered: unanswered ? 1 : void 0, sort, max } }),
222
+ reply: (commentId, text, o2) => a._req("POST", `/v1/instagram/comments/${ref(commentId)}/reply`, { body: { text }, ...idem(o2) }),
223
+ hide: (commentId, hide = true) => a._req("POST", `/v1/instagram/comments/${ref(commentId)}/hide`, { body: { hide } }),
224
+ resolve: (commentId) => a._req("POST", `/v1/instagram/comments/${ref(commentId)}/resolve`),
225
+ reopen: (commentId) => a._req("DELETE", `/v1/instagram/comments/${ref(commentId)}/resolve`)
226
+ };
227
+ this.community = { comments: ({ network, unanswered, sort, max } = {}) => a._req("GET", "/v1/comments", { query: { network, unanswered: unanswered ? 1 : void 0, sort, max } }) };
228
+ this.youtube = {
229
+ listVideos: ({ max, page } = {}) => a._req("GET", "/v1/youtube/videos", { query: { max, page } }),
230
+ getVideo: (id) => a._req("GET", `/v1/youtube/videos/${ref(id)}`),
231
+ /** `dryRun: true` returns { before, after } without writing. */
232
+ editVideo: (id, { title, description, tags, dryRun } = {}) => a._req("PATCH", `/v1/youtube/videos/${ref(id)}`, { body: { title, description, tags, dryRun } }),
233
+ /** `sort`: recent | comments | views. Returns { comments, next, pending } with `resolved` on each thread. */
234
+ comments: ({ video, unanswered, sort, max, page } = {}) => a._req("GET", "/v1/youtube/comments", { query: { video, unanswered: unanswered ? 1 : void 0, sort, max, page } }),
235
+ resolve: (threadId) => a._req("POST", `/v1/youtube/comments/${ref(threadId)}/resolve`),
236
+ reopen: (threadId) => a._req("DELETE", `/v1/youtube/comments/${ref(threadId)}/resolve`),
237
+ reply: (threadId, text, o2) => a._req("POST", `/v1/youtube/comments/${ref(threadId)}/reply`, { body: { text }, ...idem(o2) })
238
+ };
239
+ this.insights = {
240
+ /** Cards, top 5, channels, summary and the 48 h reading of the period (ISO). Server default: the last 30 days. */
241
+ get: ({ from, to, channel, tag, type, compare, sort, direction, page, limit } = {}) => a._req("GET", "/v1/insights", { query: { from, to, channel, tag, type, compare: compare ? 1 : void 0, sort, direction, page, limit } }),
242
+ /** (admin) Imports the history of YouTube, Instagram (via Buffer) and TikTok. `network`: all | youtube | instagram | tiktok; `dryRun` only counts. */
243
+ import: ({ network = "all", dryRun } = {}) => a._req("POST", "/v1/insights/import", { query: { network, dryRun: dryRun ? 1 : void 0 } })
244
+ };
245
+ this.content = { list: ({ network, type, from, to, sort, limit, offset } = {}) => a._req("GET", "/v1/content", { query: { network, type, from, to, sort, limit, offset } }) };
246
+ this.apiKeys = {
247
+ list: () => a._req("GET", "/v1/api-keys"),
248
+ create: (name, scope = "write", expiresInDays, o2) => a._req("POST", "/v1/api-keys", { body: { name, scope, ...expiresInDays ? { expiresInDays } : {} }, ...idem(o2) }),
249
+ revoke: (id) => a._req("DELETE", `/v1/api-keys/${ref(id)}`)
250
+ };
251
+ this.usage = {
252
+ /** Totals of the last `days` (1-30, default 7): { total, succeeded, errors, p95Ms, peakPerMinute, limitPerMinute, byDay, byHour (days=1), byKey }. */
253
+ get: ({ days } = {}) => a._req("GET", "/v1/usage", { query: { days } }),
254
+ /** Newest first: { records, next }. `status`: 2xx|3xx|4xx|5xx or a code; `key`: key id or "session"; `route`: substring; pass `next` as `cursor`. */
255
+ log: ({ key: key2, status, route, method, days, limit, cursor } = {}) => a._req("GET", "/v1/usage/log", { query: { key: key2, status, route, method, days, limit, cursor } })
256
+ };
257
+ this.preferences = {
258
+ get: () => a._req("GET", "/v1/preferences"),
259
+ put: ({ theme, language, notifications } = {}) => a._req("PUT", "/v1/preferences", { body: { theme, language, notifications } })
260
+ };
261
+ this.webhooks = {
262
+ list: () => a._req("GET", "/v1/webhooks"),
263
+ create: ({ url: url2, events, active, idempotencyKey } = {}) => a._req("POST", "/v1/webhooks", { body: { url: url2, events, active }, ...idem({ idempotencyKey }) }),
264
+ get: (id) => a._req("GET", `/v1/webhooks/${ref(id)}`),
265
+ update: (id, { url: url2, events, active } = {}) => a._req("PATCH", `/v1/webhooks/${ref(id)}`, { body: { url: url2, events, active } }),
266
+ remove: (id) => a._req("DELETE", `/v1/webhooks/${ref(id)}`),
267
+ /** Sends a ping.test now and returns { delivery }. */
268
+ test: (id) => a._req("POST", `/v1/webhooks/${ref(id)}/test`),
269
+ deliveries: (id, { limit } = {}) => a._req("GET", `/v1/webhooks/${ref(id)}/deliveries`, { query: { limit } }),
270
+ redeliver: (id, deliveryId, o2) => a._req("POST", `/v1/webhooks/${ref(id)}/deliveries/${ref(deliveryId)}/retry`, idem(o2)),
271
+ rotateSecret: (id, o2) => a._req("POST", `/v1/webhooks/${ref(id)}/rotate-secret`, idem(o2))
272
+ };
273
+ this.ideas = {
274
+ list: () => a._req("GET", "/v1/ideas"),
275
+ create: ({ title, note, group, tags, idempotencyKey } = {}) => a._req("POST", "/v1/ideas", { body: { title, note, group, tags }, ...idem({ idempotencyKey }) }),
276
+ update: (id, { title, note, group, tags } = {}) => a._req("PATCH", `/v1/ideas/${ref(id)}`, { body: { title, note, group, tags } }),
277
+ remove: (id) => a._req("DELETE", `/v1/ideas/${ref(id)}`),
278
+ import: (ideas, o2) => a._req("POST", "/v1/ideas/import", { body: { ideas }, ...idem(o2) })
279
+ };
280
+ addLegacy(this);
281
+ }
282
+ calendar({ from, to, channel, tag } = {}) {
283
+ return this._req("GET", "/v1/calendar", { query: { from, to, channel, tag } });
284
+ }
285
+ mentions({ network } = {}) {
286
+ return this._req("GET", "/v1/mentions", { query: { network } });
287
+ }
288
+ health() {
289
+ return this._req("GET", "/health", { noKey: true });
290
+ }
291
+ /**
292
+ * One HTTP call. `legacy: true` (only for the deprecated Portuguese aliases) skips the `X-Praeko-Contract: en` header, so the shared paths
293
+ * (/v1/posts, /v1/insights, /v1/youtube/videos) answer in the old shape.
294
+ */
295
+ async _req(method, path, { body, bytes, query, retry = method === "GET", noKey = false, legacy = false, idempotencyKey } = {}) {
296
+ if (idempotencyKey) {
297
+ retry = true;
298
+ idempotencyKey = idempotencyKey === true ? uuid() : String(idempotencyKey);
299
+ }
300
+ const q = query ? Object.entries(query).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&") : "";
301
+ const url = `${this.url}${path}${q ? "?" + q : ""}`;
302
+ const init = { method, headers: { ...noKey || this._cookies ? {} : { authorization: `Bearer ${this._key}` }, ...legacy ? {} : { "x-praeko-contract": "en" } } };
303
+ if (idempotencyKey) init.headers["idempotency-key"] = idempotencyKey;
304
+ if (bytes !== void 0) {
305
+ init.body = bytes;
306
+ init.headers["content-type"] = "application/octet-stream";
307
+ } else if (body !== void 0) {
308
+ init.body = JSON.stringify(body);
309
+ init.headers["content-type"] = "application/json";
310
+ }
311
+ let last;
312
+ for (let i = 0; i < (retry ? this._retries : 1); i++) {
313
+ if (i) await this._wait(Math.min(8e3, 500 * 2 ** i));
314
+ let r;
315
+ try {
316
+ r = await this._fetch(url, init);
317
+ } catch (e2) {
318
+ last = new PraekoError(0, "network", `network failure: ${e2.message}`);
319
+ continue;
320
+ }
321
+ const txt = await r.text();
322
+ let j = null;
323
+ try {
324
+ j = txt ? JSON.parse(txt) : null;
325
+ } catch {
326
+ }
327
+ if (r.ok) return j;
328
+ const e = j?.error ?? (j?.erro && { code: j.erro.codigo, message: j.erro.mensagem, details: j.erro.detalhes });
329
+ last = new PraekoError(r.status, e?.code || "http_error", e?.message || `HTTP ${r.status}`, e?.details);
330
+ if (!(r.status === 429 || r.status >= 500)) throw last;
331
+ }
332
+ throw last;
333
+ }
334
+ };
335
+
336
+ // ../sdk/src/node.mjs
337
+ import { closeSync, fstatSync, openSync, readSync } from "node:fs";
338
+ import { basename, extname } from "node:path";
339
+ var MIME = { ".mp4": "video/mp4", ".mov": "video/quicktime", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg" };
340
+ var mimeOf = (path) => MIME[extname(path).toLowerCase()] || null;
341
+ function fileSource(path) {
342
+ const fd = openSync(path, "r"), size = fstatSync(fd).size;
343
+ const read = async (start, n) => {
344
+ const b = Buffer.alloc(Math.min(n, size - start));
345
+ readSync(fd, b, 0, b.length, start);
346
+ return b;
347
+ };
348
+ const close = () => closeSync(fd), name = basename(path);
349
+ return { size, name, mime: mimeOf(path), read, close, tamanho: size, nome: name, ler: read, fechar: close };
350
+ }
351
+ async function uploadFile(cliente, path, { type, onProgress, tipo, aoProgredir } = {}) {
352
+ const f = fileSource(path);
353
+ try {
354
+ if (!f.mime) throw new Error(`n\xE3o sei o tipo de ${path} (use .mp4, .mov, .png ou .jpg)`);
355
+ const t = type || tipo || (f.mime.startsWith("video/") ? "video" : "cover"), cb = onProgress || aoProgredir;
356
+ if (cliente.media?.upload) return await cliente.media.upload(f, { type: t === "capa" ? "cover" : t, name: f.name, mime: f.mime, onProgress: cb });
357
+ return await cliente.midia.enviar(f, { tipo: t === "cover" ? "capa" : t, nome: f.name, mime: f.mime, aoProgredir: cb });
358
+ } finally {
359
+ f.close();
360
+ }
361
+ }
362
+
363
+ // ../sdk/src/contract.mjs
364
+ var ROUTES = [
365
+ { key: "health", method: "GET", path: "/health", legacy: "/saude", scope: "public", summary: "Liveness probe" },
366
+ { key: "health.v1", method: "GET", path: "/v1/health", legacy: "/v1/saude", scope: "public", summary: "Liveness probe (versioned path)" },
367
+ { key: "connections.callback", method: "GET", path: "/connections/:network/callback", legacy: "/conexoes/:rede/retorno", scope: "public", raw: true, summary: "OAuth redirect target (HTML page; youtube, tiktok, meta); registered redirect URIs keep working" },
368
+ { key: "media.public", method: "GET", path: "/v1/m/:id/:token/:name", legacy: null, scope: "public", raw: true, summary: "Public media by token (Range supported); also served at /m/\u2026" },
369
+ { key: "media.public", method: "HEAD", path: "/v1/m/:id/:token/:name", legacy: null, scope: "public", raw: true, summary: "Same as GET, headers only" },
370
+ { key: "connections.start", method: "GET", path: "/v1/connections/:network/start", legacy: "/v1/conexoes/:rede/iniciar", scope: "write", summary: "Start OAuth for youtube|tiktok|meta (Instagram login); for youtube it always asks for `youtube.upload`, `youtube.force-ssl` and `yt-analytics.readonly` (retention, impressions, CTR; the consent screen lets the user untick a scope, and the channel then stays `reauthorize` in the retention view); the old `analytics=1` is accepted and ignored; returns the authorization URL. A different account of the same network becomes another channel; the same account is reconnected. `meta` takes `replace=true` (replace the token of an existing connection), `add=true` (connect ANOTHER Instagram account as a new channel) and `instagram=<username>`, `instagram_id=<id|current>`, `probe=true` (report only, stores nothing)" },
371
+ { key: "connections.disconnect", method: "DELETE", path: "/v1/connections/:network", legacy: "/v1/conexoes/:rede", scope: "write", summary: "Disconnect one account of youtube|tiktok|instagram (Meta token only; the Buffer channel is never touched); `channel` picks the account, without it the default channel; other accounts, the channel and its history stay. If it was the default, another connected account takes over" },
372
+ { key: "connections.instagramToken", method: "PUT", path: "/v1/connections/instagram/token", legacy: "/v1/conexoes/instagram/token", scope: "admin", body: "json", summary: "Store the Meta API token for Instagram (encrypted)" },
373
+ { key: "channels.list", method: "GET", path: "/v1/channels", legacy: "/v1/canais", scope: "read", summary: "One item per connected channel (several accounts of a network = several items, `default` marks the one used without `channel`), plus a not-connected item for a network with none; status and slots count" },
374
+ { key: "channels.default", method: "PUT", path: "/v1/channels/:channel/default", legacy: "/v1/canais/:canal/padrao", scope: "write", summary: "Make this account the default of its network (used when a network route has no `channel`); the other accounts of the same network lose the mark. 404 channel_not_found if it is not an active channel of this workspace" },
375
+ { key: "channels.remove", method: "DELETE", path: "/v1/channels/:channel", legacy: "/v1/canais/:canal", scope: "write", summary: "Remove a DISCONNECTED account from the list (history stays; refused while connected, with pending items, or for the Buffer Instagram channel)" },
376
+ { key: "channels.avatar", method: "GET", path: "/v1/channels/:network/avatar", legacy: "/v1/canais/:rede/avatar", scope: "read", summary: "Channel avatar image (`channel` picks the account)" },
377
+ { key: "tiktok.creator", method: "GET", path: "/v1/tiktok/creator", legacy: "/v1/tiktok/criador", scope: "read", summary: "What the connected TikTok account accepts (creator_info); `channel` picks the account" },
378
+ { key: "channels.slots.get", method: "GET", path: "/v1/channels/:network/slots", legacy: "/v1/canais/:rede/vagas", scope: "read", summary: "Weekly publishing slots of a channel (`channel` picks the account, else the default)" },
379
+ { key: "channels.slots.put", method: "PUT", path: "/v1/channels/:network/slots", legacy: "/v1/canais/:rede/vagas", scope: "write", body: "json", summary: "Replace the weekly slots of one channel (`channel`; reorganizes its queued items)" },
380
+ { key: "channels.followers.put", method: "PUT", path: "/v1/channels/:network/followers", legacy: "/v1/canais/:rede/seguidores", scope: "write", body: "json", summary: "Manual follower count (Instagram only; `channel` picks the account)" },
381
+ { key: "channels.followers.get", method: "GET", path: "/v1/channels/:network/followers", legacy: "/v1/canais/:rede/seguidores", scope: "read", summary: "Last 60 daily follower records (`channel` picks the account)" },
382
+ { key: "media.create", method: "POST", path: "/v1/media", legacy: "/v1/midia", scope: "write", body: "json", summary: "Start a media upload" },
383
+ { key: "media.list", method: "GET", path: "/v1/media", legacy: "/v1/midia", scope: "read", summary: "List media (last 100); older videos get durationMs/width/height filled on first read" },
384
+ { key: "media.get", method: "GET", path: "/v1/media/:id", legacy: "/v1/midia/:id", scope: "read", summary: "One media item (with publicUrl when ready; videos also carry durationMs, width and height when the container header says so)" },
385
+ { key: "media.part", method: "PUT", path: "/v1/media/:id/parts/:part", legacy: "/v1/midia/:id/partes/:n", scope: "write", body: "bytes", summary: "Upload one part (raw bytes)" },
386
+ { key: "media.content.put", method: "PUT", path: "/v1/media/:id/content", legacy: "/v1/midia/:id/conteudo", scope: "write", body: "bytes", summary: "Single-request upload (media created with simple: true)" },
387
+ { key: "media.content.get", method: "GET", path: "/v1/media/:id/content", legacy: "/v1/midia/:id/conteudo", scope: "read", summary: "The file (Bearer, Range supported)" },
388
+ { key: "media.content.head", method: "HEAD", path: "/v1/media/:id/content", legacy: "/v1/midia/:id/conteudo", scope: "read", summary: "Same as GET, headers only" },
389
+ { key: "media.complete", method: "POST", path: "/v1/media/:id/complete", legacy: "/v1/midia/:id/completar", scope: "write", summary: "Join the parts" },
390
+ { key: "media.delete", method: "DELETE", path: "/v1/media/:id", legacy: "/v1/midia/:id", scope: "write", summary: "Remove a media item (refused while used by a pending post)" },
391
+ { key: "posts.create", method: "POST", path: "/v1/posts", legacy: "/v1/posts", scope: "write", body: "json", summary: "Create a post with one item per network (queue, fixed time or draft); an item may carry `channel` (account of that network; default channel without it)" },
392
+ { key: "posts.adopt", method: "POST", path: "/v1/posts/adopt", legacy: "/v1/posts/adotar", scope: "write", body: "json", summary: "Register a Reel already scheduled in Buffer (does not call Buffer)" },
393
+ { key: "posts.list", method: "GET", path: "/v1/posts", legacy: "/v1/posts", scope: "read", summary: "List posts (filters: state, channel, tag)" },
394
+ { key: "posts.get", method: "GET", path: "/v1/posts/:id", legacy: "/v1/posts/:id", scope: "read", summary: "One post with its items and actions" },
395
+ { key: "posts.update", method: "PATCH", path: "/v1/posts/:id", legacy: "/v1/posts/:id", scope: "write", body: "json", summary: "Edit title, texts, media, tags, items; move to draft/queue" },
396
+ { key: "posts.cancel", method: "DELETE", path: "/v1/posts/:id", legacy: "/v1/posts/:id", scope: "write", summary: "Cancel the pending items of a post" },
397
+ { key: "posts.publishNow", method: "POST", path: "/v1/posts/:id/publish-now", legacy: "/v1/posts/:id/publicar-agora", scope: "write", summary: "Take pending items out of the queue and publish now (?network=)" },
398
+ { key: "posts.youtubeSchedule", method: "POST", path: "/v1/posts/:id/youtube/schedule", legacy: "/v1/posts/:id/youtube/agendar", scope: "write", body: "json", summary: "Schedule publication of a video already uploaded to YouTube (private)" },
399
+ { key: "posts.itemComment", method: "POST", path: "/v1/posts/:id/items/:item/comment", legacy: "/v1/posts/:id/itens/:destino/comentar", scope: "write", summary: "Release the stored pinned comment of an item" },
400
+ { key: "calendar.get", method: "GET", path: "/v1/calendar", legacy: "/v1/calendario", scope: "read", summary: "Items in a period plus free slots" },
401
+ { key: "insights.get", method: "GET", path: "/v1/insights", legacy: "/v1/insights", scope: "read", summary: "Cards, top posts, per-channel results (one row per account when a network has several; `channel` filters one account), summary, 48 h reading" },
402
+ { key: "insights.retention", method: "GET", path: "/v1/insights/retention", legacy: "/v1/insights/retention", scope: "read", summary: "Retention, impressions and CTR per post of the period (`from`, `to`, `channel`, `limit` 1-200). YouTube: audience retention (YouTube Analytics API), thumbnail impressions and CTR (YouTube Reporting API); Instagram: reach, watch time and skip rate (Meta API). `channels[].state` says whether each account can deliver them: `ready`, `pending` (first report still being generated), `reauthorize` (reconnect YouTube once and keep the Analytics permission ticked), `unavailable` or `error`; a missing number is `null`, never 0" },
403
+ { key: "insights.retention.item", method: "GET", path: "/v1/insights/:item/retention", legacy: "/v1/insights/:item/retention", scope: "read", summary: "One post (item id or imported content id) with its retention `curve`: points of `position` (0..1 of the video), `watched` (audienceWatchRatio, above 1 when viewers rewatch) and `relative` (relativeRetentionPerformance, 0.5 = median of similar-length videos)" },
404
+ { key: "insights.import", method: "POST", path: "/v1/insights/import", legacy: "/v1/insights/importar", scope: "admin", query: { network: { all: "tudo" } }, summary: "Import history from YouTube, Buffer (Instagram) and TikTok (every connected account; `channel` with a network limits it to one)" },
405
+ { key: "content.list", method: "GET", path: "/v1/content", legacy: "/v1/conteudo", scope: "read", summary: "Everything already published (Praeko + imported)" },
406
+ { key: "youtube.videos", method: "GET", path: "/v1/youtube/videos", legacy: "/v1/youtube/videos", scope: "read", summary: "Channel videos (including private and scheduled); `channel` picks the account" },
407
+ { key: "youtube.video", method: "GET", path: "/v1/youtube/videos/:id", legacy: "/v1/youtube/videos/:id", scope: "read", summary: "One video" },
408
+ { key: "youtube.editVideo", method: "PATCH", path: "/v1/youtube/videos/:id", legacy: "/v1/youtube/videos/:id", scope: "write", body: "json", summary: "Edit title/description/tags (dryRun: true returns before/after only)" },
409
+ { key: "youtube.comments", method: "GET", path: "/v1/youtube/comments", legacy: "/v1/youtube/comentarios", scope: "read", summary: "Comment threads (`channel` picks the account)" },
410
+ { key: "youtube.reply", method: "POST", path: "/v1/youtube/comments/:id/reply", legacy: "/v1/youtube/comentarios/:id/responder", scope: "write", body: "json", summary: "Reply to a thread (writes to YouTube)" },
411
+ { key: "youtube.resolve", method: "POST", path: "/v1/youtube/comments/:id/resolve", legacy: "/v1/youtube/comentarios/:id/resolver", scope: "write", summary: "Mark a thread as resolved (Praeko only)" },
412
+ { key: "youtube.reopen", method: "DELETE", path: "/v1/youtube/comments/:id/resolve", legacy: "/v1/youtube/comentarios/:id/resolver", scope: "write", summary: "Reopen a resolved thread" },
413
+ { key: "instagram.media", method: "GET", path: "/v1/instagram/media", legacy: "/v1/instagram/midias", scope: "read", summary: "Profile media grid (Meta API)" },
414
+ { key: "instagram.comments", method: "GET", path: "/v1/instagram/comments", legacy: "/v1/instagram/comentarios", scope: "read", summary: "Instagram comments (Meta API)" },
415
+ { key: "instagram.reply", method: "POST", path: "/v1/instagram/comments/:id/reply", legacy: "/v1/instagram/comentarios/:id/responder", scope: "write", body: "json", summary: "Reply to a comment (writes to Instagram)" },
416
+ { key: "instagram.hide", method: "POST", path: "/v1/instagram/comments/:id/hide", legacy: "/v1/instagram/comentarios/:id/ocultar", scope: "write", body: "json", summary: "Hide or unhide a comment" },
417
+ { key: "instagram.resolve", method: "POST", path: "/v1/instagram/comments/:id/resolve", legacy: "/v1/instagram/comentarios/:id/resolver", scope: "write", summary: "Mark a comment as resolved (Praeko only)" },
418
+ { key: "instagram.reopen", method: "DELETE", path: "/v1/instagram/comments/:id/resolve", legacy: "/v1/instagram/comentarios/:id/resolver", scope: "write", summary: "Reopen a resolved comment" },
419
+ { key: "comments.list", method: "GET", path: "/v1/comments", legacy: "/v1/comentarios", scope: "read", query: { network: { all: "todas" } }, summary: "Comments of all networks together, from EVERY active account of each network (each comment carries `channel`; `channel` filters one account; an account that fails goes to `errors`, the others still answer)" },
420
+ { key: "mentions.list", method: "GET", path: "/v1/mentions", legacy: "/v1/mencoes", scope: "read", query: { network: { all: "todas" } }, summary: "Mentions (Instagram by tag; YouTube has none)" },
421
+ { key: "apiKeys.list", method: "GET", path: "/v1/api-keys", legacy: "/v1/chaves", scope: "admin", summary: "List API keys (never the secret)" },
422
+ { key: "apiKeys.create", method: "POST", path: "/v1/api-keys", legacy: "/v1/chaves", scope: "admin", body: "json", summary: "Create a key (the secret is shown once)" },
423
+ { key: "apiKeys.revoke", method: "DELETE", path: "/v1/api-keys/:id", legacy: "/v1/chaves/:id", scope: "admin", summary: "Revoke a key" },
424
+ // Rotas nascidas depois do contrato novo: sem caminho em português; o "legacy" é o mesmo caminho (sem o cabeçalho, a forma interna), como /v1/insights.
425
+ { key: "usage.get", method: "GET", path: "/v1/usage", legacy: "/v1/usage", scope: "read", summary: "API usage of this workspace over the last `days` (1-30, default 7, UTC): total, succeeded (< 400), errors, p95 duration, peak requests in one minute vs the per-minute limit, `byDay`, `byHour` (only with days=1: the last 24 hours) and `byKey` (`session` = the panel)" },
426
+ { key: "usage.log", method: "GET", path: "/v1/usage/log", legacy: "/v1/usage/log", scope: "read", summary: "Request log, newest first, kept 30 days; metadata only (method, route template without ids, status, duration, key), never bodies, headers or query. Filters: `key` (id or `session`), `status` (`2xx`|`3xx`|`4xx`|`5xx` or a code), `route` (substring), `method`, `days` (1-30); `limit` (1-200, default 50) and `cursor` (the `next` of the previous page)" },
427
+ { key: "preferences.get", method: "GET", path: "/v1/preferences", legacy: "/v1/preferences", scope: "read", summary: "Panel preferences of the signed-in user in this workspace (an API key reads the workspace row): `theme` (light, dark, system), `language` (pt-BR, en) and `notifications` per event (`postSent`, `postFailed`, `scheduledFail`, `weeklySummary`, `newComment`) with `email` and `panel` flags. Missing values come back as the defaults (dark, pt-BR, no e-mail, panel on)" },
428
+ { key: "preferences.put", method: "PUT", path: "/v1/preferences", legacy: "/v1/preferences", scope: "write", body: "json", summary: "Change preferences; partial (only the fields sent change) and strict (unknown field or value is 422 invalid_field). Returns the full preferences. `postSent`, `postFailed` and `scheduledFail` e-mails are sent by the server when `email` is true; `weeklySummary` and `newComment` are stored but no sender exists yet" },
429
+ { key: "webhooks.list", method: "GET", path: "/v1/webhooks", legacy: "/v1/webhooks", scope: "admin", summary: "Outgoing webhooks of this workspace (never the secret: only `secretHint`), with the delivery success of the last 7 days (`recentDeliveries`) and the subscribable `events`" },
430
+ { key: "webhooks.create", method: "POST", path: "/v1/webhooks", legacy: "/v1/webhooks", scope: "admin", body: "json", summary: "Create a webhook: `url` (https, public host; private IPs, localhost and cloud metadata are refused with unsafe_url), `events` (post.sent, post.failed, post.scheduled-fail; default the first two), `active`. The signing `secret` is returned ONCE. Max 10 per workspace" },
431
+ { key: "webhooks.get", method: "GET", path: "/v1/webhooks/:id", legacy: "/v1/webhooks/:id", scope: "admin", summary: "One webhook (no secret)" },
432
+ { key: "webhooks.update", method: "PATCH", path: "/v1/webhooks/:id", legacy: "/v1/webhooks/:id", scope: "admin", body: "json", summary: "Change `url`, `events` or `active`. Pausing discards the pending deliveries; re-activating resets the failure count" },
433
+ { key: "webhooks.delete", method: "DELETE", path: "/v1/webhooks/:id", legacy: "/v1/webhooks/:id", scope: "admin", summary: "Delete a webhook and its delivery history" },
434
+ { key: "webhooks.test", method: "POST", path: "/v1/webhooks/:id/test", legacy: "/v1/webhooks/:id/test", scope: "admin", summary: "Send a `ping.test` event now (even when paused) and return the `delivery` result; it does not count towards auto-disable" },
435
+ { key: "webhooks.deliveries", method: "GET", path: "/v1/webhooks/:id/deliveries", legacy: "/v1/webhooks/:id/deliveries", scope: "admin", summary: "Recent deliveries, newest first (`limit` 1-100, default 20; kept 30 days): event, state (pending, delivered, failed, discarded), attempts, last HTTP status and error, duration" },
436
+ { key: "webhooks.redeliver", method: "POST", path: "/v1/webhooks/:id/deliveries/:delivery/retry", legacy: "/v1/webhooks/:id/deliveries/:delivery/retry", scope: "admin", summary: "Send one delivery again now, with the same `Praeko-Delivery` id (receivers can deduplicate)" },
437
+ { key: "webhooks.rotateSecret", method: "POST", path: "/v1/webhooks/:id/rotate-secret", legacy: "/v1/webhooks/:id/rotate-secret", scope: "admin", summary: "Replace the signing secret; the new `secret` is returned ONCE and the old one stops signing immediately" },
438
+ { key: "ideas.list", method: "GET", path: "/v1/ideas", legacy: "/v1/ideas", scope: "read", summary: 'Idea board of this workspace (the "Create" screen), newest first: `title`, `note`, `group` (idea, scripting, ready, posted), `tags` (the first one is the series), `createdAt`, `updatedAt`' },
439
+ { key: "ideas.create", method: "POST", path: "/v1/ideas", legacy: "/v1/ideas", scope: "write", body: "json", summary: "Add an idea: `title` (1-200 characters, required), `note` (up to 5000), `group` (default idea), `tags` (up to 20, 40 characters each). Max 2000 per workspace" },
440
+ { key: "ideas.import", method: "POST", path: "/v1/ideas/import", legacy: "/v1/ideas/import", scope: "write", body: "json", summary: "Bulk-add `ideas` (up to 500 per call), as the panel does with the ideas it kept in the browser. An item whose `id` was already imported is skipped (`skippedCount`), so repeating the call never duplicates. Items without a valid `title` are skipped" },
441
+ { key: "ideas.update", method: "PATCH", path: "/v1/ideas/:id", legacy: "/v1/ideas/:id", scope: "write", body: "json", summary: "Change `title`, `note`, `group` (moving a card) or `tags`; omitted fields stay" },
442
+ { key: "ideas.delete", method: "DELETE", path: "/v1/ideas/:id", legacy: "/v1/ideas/:id", scope: "write", summary: "Delete an idea" }
443
+ ];
444
+ var toRegExp = (tpl) => new RegExp("^" + tpl.replace(/:[A-Za-z_]+/g, "([^/]+)").replace(/\//g, "\\/") + "$");
445
+ var NEW_RE = ROUTES.map((r) => ({ r, re: toRegExp(r.path) }));
446
+ var OLD_RE = ROUTES.filter((r) => r.legacy).map((r) => ({ r, re: toRegExp(r.legacy) }));
447
+ var KEYS = [
448
+ ["abs", "absolute"],
449
+ ["acao", "action"],
450
+ ["acoes", "actions"],
451
+ ["adotado", "adopted"],
452
+ ["agregadoAtualizadoEm", "aggregateUpdatedAt"],
453
+ ["agregadoBuffer", "bufferAggregate"],
454
+ ["alcance", "reach"],
455
+ ["anterior", "previous"],
456
+ ["antes", "before"],
457
+ ["appAuditado", "appAudited"],
458
+ ["ate", "to"],
459
+ ["ativa", "active"],
460
+ ["atual", "current"],
461
+ ["atualizado_em", "updatedAt"],
462
+ ["atualizados", "updated"],
463
+ ["autor", "author"],
464
+ ["autorCanal", "authorChannel"],
465
+ ["aviso", "warning"],
466
+ ["avisarInscritos", "notifySubscribers"],
467
+ ["canais", "channels"],
468
+ ["canal", "channel"],
469
+ ["canalId", "channelId"],
470
+ ["cancelados", "canceled"],
471
+ ["cancelar", "cancel"],
472
+ ["capa", "cover"],
473
+ ["capaNoQuadro", "coverInFrame"],
474
+ ["cartoes", "cards"],
475
+ ["categoriaId", "categoryId"],
476
+ ["chave", "key"],
477
+ ["chaves", "keys"],
478
+ ["codigo", "code"],
479
+ ["coleta", "collection"],
480
+ ["coletando", "collecting"],
481
+ ["comentar", "comment", "req"],
482
+ ["comentario", "comment", "res"],
483
+ ["comentarioDesabilitado", "commentsDisabled"],
484
+ ["comentarioFixo", "pinnedComment"],
485
+ ["comentarios", "comments"],
486
+ ["compartilhamentos", "shares"],
487
+ ["compartilharNoFeed", "shareToFeed"],
488
+ ["comparar", "compare"],
489
+ ["conectado", "connected"],
490
+ ["conectadoEm", "connectedAt"],
491
+ ["consultadoEm", "queriedAt"],
492
+ ["conteudoComercial", "commercialContent"],
493
+ ["criado_em", "createdAt"],
494
+ ["curtidas", "likes"],
495
+ ["de", "from"],
496
+ ["depois", "after"],
497
+ ["deslocamento", "offset"],
498
+ ["desabilitarComentarios", "disableComments"],
499
+ ["desabilitarDueto", "disableDuet"],
500
+ ["desabilitarStitch", "disableStitch"],
501
+ ["desconectado", "disconnected"],
502
+ ["descricao", "description"],
503
+ ["destino_id", "itemId"],
504
+ ["destinos", "items", "req"],
505
+ ["detalhe", "detail"],
506
+ ["detalhes", "details"],
507
+ ["dia", "day"],
508
+ ["dia_semana", "weekday"],
509
+ ["direcao", "direction"],
510
+ ["diretoDisponivel", "directAvailable"],
511
+ ["doDono", "fromOwner"],
512
+ ["duetoDesabilitado", "duetDisabled"],
513
+ ["duracaoMaxSeg", "maxDurationSec"],
514
+ ["editarHorario", "editSchedule"],
515
+ ["editarTexto", "editText"],
516
+ ["encontrados", "found"],
517
+ ["engajamento", "engagement"],
518
+ ["engajamentoGeral", "overallEngagement"],
519
+ ["enviar", "uploadHint"],
520
+ ["erro", "error"],
521
+ ["erros", "errors"],
522
+ ["escopo", "scope"],
523
+ ["escrito", "written"],
524
+ ["estado", "state"],
525
+ ["etiqueta", "tag"],
526
+ ["etiquetas", "tags"],
527
+ ["executarEm", "runAt"],
528
+ ["expiraEm", "expiresAt"],
529
+ ["expiraEmDias", "expiresInDays", "req"],
530
+ ["expirada", "expired"],
531
+ ["diasRestantes", "daysLeft"],
532
+ ["externo", "external"],
533
+ ["fila", "queue"],
534
+ ["filtros", "filters"],
535
+ ["fonte", "source"],
536
+ ["fontes", "sources"],
537
+ ["formato", "format"],
538
+ ["fuso", "timezone"],
539
+ ["hora", "time"],
540
+ ["ignorado", "skipped"],
541
+ ["importados", "imported"],
542
+ ["itens", "items", "res"],
543
+ ["jaFeita", "alreadyDone"],
544
+ ["legenda", "caption"],
545
+ ["leitura48h", "reading48h"],
546
+ ["leitura48hTexto", "reading48hText"],
547
+ ["leiturasNoBanco", "storedReadings"],
548
+ ["lembrete", "reminder"],
549
+ ["limite", "limit"],
550
+ ["mantidos", "kept"],
551
+ ["marcaPropria", "ownBrand"],
552
+ ["marcaTerceiros", "thirdPartyBrand"],
553
+ ["marcos", "milestones"],
554
+ ["mencoes", "mentions"],
555
+ ["mensagem", "message"],
556
+ ["midia", "media"],
557
+ ["midiaId", "mediaId"],
558
+ ["midias", "mediaItems"],
559
+ ["miniatura", "thumb"],
560
+ ["miniaturaMidia", "thumbMedia"],
561
+ ["miniaturaMs", "coverFrameMs"],
562
+ ["miniaturaTipo", "thumbType"],
563
+ ["modo", "mode"],
564
+ ["motivo", "reason"],
565
+ ["mover", "moveTo"],
566
+ ["moverRascunho", "moveToDraft"],
567
+ ["envio", "delivery"],
568
+ ["revogacaoPulada", "revocationSkipped"],
569
+ ["removido", "accountRemoved"],
570
+ ["motivoFalha", "failReason"],
571
+ ["verificadoEm", "checkedAt"],
572
+ ["mudou", "changed"],
573
+ ["naCaixaDeEntrada", "inInbox"],
574
+ ["nome", "name"],
575
+ ["novos", "created"],
576
+ ["ocultar", "hide"],
577
+ ["oculto", "hidden"],
578
+ ["opcoes", "options"],
579
+ ["ordem", "sort"],
580
+ ["origem", "origin"],
581
+ ["pagina", "page"],
582
+ ["paginas", "pages"],
583
+ ["parte", "part"],
584
+ ["parteMaxima", "maxPartSize"],
585
+ ["parteMinima", "minPartSize"],
586
+ ["padrao", "default"],
587
+ ["partes", "parts"],
588
+ ["parteSugerida", "suggestedPartSize"],
589
+ ["pendentes", "pending"],
590
+ ["pendentesPorRede", "pendingByNetwork"],
591
+ ["pendentesPorCanal", "pendingByChannel"],
592
+ ["periodo", "period"],
593
+ ["periodoAnterior", "previousPeriod"],
594
+ ["postsAnterior", "previousPosts"],
595
+ ["postsComMetricas", "postsWithMetrics"],
596
+ ["postsSemMetricas", "postsWithoutMetrics"],
597
+ ["prefixo", "prefix"],
598
+ ["privacidade", "privacy"],
599
+ ["privacidades", "privacyLevels"],
600
+ ["privacidadesPermitidas", "allowedPrivacyLevels"],
601
+ ["provedor", "provider"],
602
+ ["proxima", "next"],
603
+ ["publicadoEm", "publishedAt"],
604
+ ["publicaPelaMeta", "publishesViaMeta"],
605
+ ["publicarAgora", "publishNow"],
606
+ ["quando", "scheduledFor"],
607
+ ["rascunho", "draft"],
608
+ ["reacoes", "reactions"],
609
+ ["reconciliados", "reconciled"],
610
+ ["rede", "network"],
611
+ ["registros", "records"],
612
+ ["removida", "removed"],
613
+ ["reorganizados", "rescheduled"],
614
+ ["resolvido", "resolved"],
615
+ ["respondido", "answered"],
616
+ ["respostas", "replies"],
617
+ ["resultado", "result"],
618
+ ["resumo", "summary"],
619
+ ["revogada", "revoked"],
620
+ ["revogada_em", "revokedAt"],
621
+ ["revogadoNoTiktok", "revokedAtTiktok"],
622
+ ["revogadoNoGoogle", "revokedAtGoogle"],
623
+ ["novoPadrao", "newDefault"],
624
+ ["salvamentos", "saves"],
625
+ ["seguidores", "followers"],
626
+ ["seguidoresGanhos", "followersGained"],
627
+ ["seguidoresVar", "followersChange"],
628
+ ["semMetricas", "noMetrics"],
629
+ ["semResposta", "unanswered"],
630
+ ["servico", "service"],
631
+ ["simples", "simple"],
632
+ ["simulado", "simulated"],
633
+ ["simular", "dryRun"],
634
+ ["stitchDesabilitado", "stitchDisabled"],
635
+ ["substitui", "replaces"],
636
+ ["suportado", "supported"],
637
+ ["tamanho", "size"],
638
+ ["tempoAssistidoMin", "watchTimeMin"],
639
+ ["tempoMedioSeg", "avgWatchTimeSec"],
640
+ ["texto", "text"],
641
+ ["texto_base", "baseText"],
642
+ ["textos", "texts"],
643
+ ["tipo", "type"],
644
+ ["titulo", "title"],
645
+ ["total", "total"],
646
+ ["totalRespostas", "replyCount"],
647
+ ["ultima", "last"],
648
+ ["ultimaImportacao", "lastImport"],
649
+ ["ultimo_uso", "lastUsedAt"],
650
+ ["urlPublica", "publicUrl"],
651
+ ["usuario", "username"],
652
+ ["vagas", "slots"],
653
+ ["vagasLivres", "freeSlots"],
654
+ ["valor", "value"],
655
+ ["variacao", "change"],
656
+ ["verificar", "verify"],
657
+ ["versao", "version"],
658
+ ["visualizacoes", "viewCount"],
659
+ ["ignorados", "skippedCount"],
660
+ ["publicos", "publicCount"],
661
+ // uso e log de requisições (/v1/usage)
662
+ ["dias", "days"],
663
+ ["sucesso", "succeeded"],
664
+ ["picoPorMinuto", "peakPerMinute"],
665
+ ["limitePorMinuto", "limitPerMinute"],
666
+ ["porDia", "byDay"],
667
+ ["porHora", "byHour"],
668
+ ["porChave", "byKey"],
669
+ ["inicio", "start"],
670
+ ["ultimoEm", "lastAt"],
671
+ ["metodo", "method"],
672
+ ["rota", "route"],
673
+ ["duracaoMs", "durationMs"],
674
+ ["largura", "width"],
675
+ ["altura", "height"],
676
+ ["chaveNome", "keyName"],
677
+ // webhooks (/v1/webhooks)
678
+ ["eventos", "events"],
679
+ ["evento", "event"],
680
+ ["eventoId", "eventId"],
681
+ ["segredo", "secret"],
682
+ ["segredoDica", "secretHint"],
683
+ ["falhasSeguidas", "consecutiveFailures"],
684
+ ["desativadoMotivo", "disabledReason"],
685
+ ["recentes", "recentDeliveries"],
686
+ ["entregas", "deliveries"],
687
+ ["tentativas", "attempts"],
688
+ ["proximaTentativa", "nextAttemptAt"],
689
+ ["ultimoStatus", "lastStatus"],
690
+ ["ultimoErro", "lastError"],
691
+ ["entregueEm", "deliveredAt"],
692
+ ["apagado", "deleted"],
693
+ // banco de ideias (/v1/ideas)
694
+ ["ideias", "ideas"],
695
+ ["nota", "note"],
696
+ ["grupo", "group"],
697
+ // retenção, impressões e CTR (/v1/insights/retention)
698
+ ["retencao", "retention"],
699
+ ["curva", "curve"],
700
+ ["impressoes", "impressions"],
701
+ ["posicao", "position"],
702
+ ["assistida", "watched"],
703
+ ["relativa", "relative"],
704
+ ["percentualMedio", "avgViewPercentage"],
705
+ ["taxaPulo", "skipRate"],
706
+ ["desde", "since"],
707
+ ["pontos", "points"],
708
+ ["coletadoEm", "collectedAt"]
709
+ ].filter((x, i, a) => a.findIndex((y) => y[0] === x[0]) === i);
710
+ var SCOPED_KEYS = { reorganizados: [["de", "from"], ["para", "to"]], leitura48h: [["destinoId", "itemId"]] };
711
+ var SAME_KEYS = /* @__PURE__ */ new Set(["id", "ctr", "item", "url", "views", "tags", "meta", "host", "token", "userId", "ok", "total", "pct", "manual", "custom", "studio", "video", "mime", "etag", "avatar", "youtube", "instagram", "tiktok", "publishAt", "parentId", "permalink", "thumbnail", "posts", "topPosts", "videos", "redirect", "chunkMB", "via", "videoId", "postId", "threadId", "limit", "max", "publishId", "status", "p95Ms", "cursor", "webhooks", "theme", "language", "notifications", "email", "panel", "postSent", "postFailed", "scheduledFail", "weeklySummary", "newComment"]);
712
+ var VALUES = {
713
+ estado: [
714
+ ["rascunho", "draft"],
715
+ ["agendado", "scheduled"],
716
+ ["enviado", "sent"],
717
+ ["erro", "error"],
718
+ ["cancelado", "canceled"],
719
+ ["privado", "private"],
720
+ ["fila", "queued"],
721
+ ["enviando", "uploading"],
722
+ ["pronta", "ready"],
723
+ ["removida", "removed"],
724
+ ["pendente", "pending"],
725
+ ["reautorizar", "reauthorize"],
726
+ ["indisponivel", "unavailable"],
727
+ ["entregue", "delivered"],
728
+ ["falhou", "failed"],
729
+ ["descartada", "discarded"]
730
+ ],
731
+ // os quatro últimos: entregas de webhook
732
+ grupo: [["sem", "idea"], ["fazer", "scripting"], ["andamento", "ready"], ["feito", "posted"]],
733
+ // grupos do quadro de ideias (Ideia, Roteirizando, Pronto para postar, Postado)
734
+ modo: [["privado", "private"], ["agendado", "scheduled"], ["publico", "public"], ["rascunho", "draft"], ["direto", "direct"], ["agora", "now"]],
735
+ mover: [["rascunho", "draft"], ["fila", "queue"]],
736
+ tipo: [["capa", "cover"], ["longo", "long"], ["comentar", "comment"], ["agendar", "schedule"]],
737
+ formato: [["longo", "long"]],
738
+ dia: [
739
+ ["dom", "sun"],
740
+ ["seg", "mon"],
741
+ ["ter", "tue"],
742
+ ["qua", "wed"],
743
+ ["qui", "thu"],
744
+ ["sex", "fri"],
745
+ ["sab", "sat"],
746
+ ["dom", "sunday", "req"],
747
+ ["seg", "monday", "req"],
748
+ ["ter", "tuesday", "req"],
749
+ ["qua", "wednesday", "req"],
750
+ ["qui", "thursday", "req"],
751
+ ["sex", "friday", "req"],
752
+ ["sab", "saturday", "req"]
753
+ ],
754
+ escopo: [["leitura", "read"], ["escrita", "write"], ["admin", "admin"]],
755
+ origem: [["externo", "external"], ["importado", "imported"]],
756
+ ordem: [
757
+ ["recentes", "recent"],
758
+ ["comentarios", "comments"],
759
+ ["reacoes", "reactions"],
760
+ ["publicadoEm", "publishedAt"],
761
+ ["engajamento", "engagement"],
762
+ ["compartilhamentos", "shares"],
763
+ ["salvamentos", "saves"],
764
+ ["alcance", "reach"]
765
+ ],
766
+ rede: [["todas", "all"], ["tudo", "all"]],
767
+ direcao: [],
768
+ fonte: [["marco48h", "milestone48h"], ["atual", "current"], ["sem-dados", "no-data"]],
769
+ detalhe: [
770
+ ["reserva no Buffer", "buffer-fallback"],
771
+ ["publicado", "published"],
772
+ ["aguardando hor\xE1rio", "awaiting-time"],
773
+ ["publicando", "publishing"],
774
+ ["preparado", "prepared"],
775
+ ["preparando", "preparing"],
776
+ ["aguardando preparo", "awaiting-prep"]
777
+ ]
778
+ };
779
+ var SCOPED_VALUES = {
780
+ acoes: { estado: [["agendada", "pending"], ["executando", "running"], ["feita", "done"], ["erro", "error"], ["cancelada", "canceled"]] }
781
+ };
782
+ var PATH_KEYS = /* @__PURE__ */ new Set(["avatar", "enviar"]);
783
+ var PATH_PAIRS = [["/v1/canais/", "/v1/channels/"], ["?canal=", "?channel="], ["/v1/midia", "/v1/media"], ["/conteudo", "/content"], ["/partes/", "/parts/"]];
784
+ var rewritePath = (s, dir) => PATH_PAIRS.reduce((x, [pt, en]) => dir === "toEN" ? x.split(pt).join(en) : x.split(en).join(pt), s);
785
+ var index = (rows, dir) => {
786
+ const mk = (kind) => {
787
+ const m = /* @__PURE__ */ new Map();
788
+ for (const [pt, en, only] of rows) {
789
+ if (only && only !== kind) continue;
790
+ if (dir === "toEN") {
791
+ if (!m.has(pt)) m.set(pt, en);
792
+ } else if (!m.has(en)) m.set(en, pt);
793
+ }
794
+ return m;
795
+ };
796
+ return { req: mk("req"), res: mk("res") };
797
+ };
798
+ var KEY_IDX = { toEN: index(KEYS, "toEN"), toPT: index(KEYS, "toPT") };
799
+ var VAL_IDX = Object.fromEntries(Object.entries(VALUES).map(([k, rows]) => [k, { toEN: index(rows, "toEN"), toPT: index(rows, "toPT") }]));
800
+ var SKEY_IDX = Object.fromEntries(Object.entries(SCOPED_KEYS).map(([k, rows]) => [k, { toEN: index(rows, "toEN"), toPT: index(rows, "toPT") }]));
801
+ var SVAL_IDX = Object.fromEntries(Object.entries(SCOPED_VALUES).map(([p, fields]) => [p, Object.fromEntries(Object.entries(fields).map(([k, rows]) => [k, { toEN: index(rows, "toEN"), toPT: index(rows, "toPT") }]))]));
802
+ var PT_ONLY_KEYS = new Set(KEYS.filter(([pt, en]) => pt !== en).map(([pt]) => pt));
803
+ var EN_KEYS = new Set(KEYS.map(([, en]) => en));
804
+ for (const k of EN_KEYS) PT_ONLY_KEYS.delete(k);
805
+ var EN_ALSO = new Set(Object.entries(VALUES).flatMap(([campo, rows]) => rows.map((r) => `${campo}|${r[1]}`)));
806
+ var PtKeyError = class extends Error {
807
+ constructor(key, value) {
808
+ super(value === void 0 ? `o campo "${key}" n\xE3o existe no contrato novo` : `o valor "${value}" de "${key}" n\xE3o existe no contrato novo`);
809
+ this.name = "PtKeyError";
810
+ this.key = key;
811
+ this.value = value;
812
+ }
813
+ };
814
+ var PT_ONLY_VALUES = {};
815
+ for (const [campo, rows] of Object.entries(VALUES)) {
816
+ const en = new Set(rows.map((r) => r[1]));
817
+ PT_ONLY_VALUES[campo] = new Set(rows.filter((r) => !r[2] || r[2] === "req").map((r) => r[0]).filter((pt) => !en.has(pt)));
818
+ }
819
+ for (const [pai, campos] of Object.entries(SCOPED_VALUES)) for (const [campo, rows] of Object.entries(campos)) {
820
+ const en = new Set(rows.map((r) => r[1]));
821
+ PT_ONLY_VALUES[`${pai}.${campo}`] ||= new Set(rows.map((r) => r[0]).filter((pt) => !en.has(pt)));
822
+ }
823
+ function translate(value, o, container = null, own = null) {
824
+ const { dir, kind } = o;
825
+ if (Array.isArray(value)) return value.map((x) => translate(x, o, container, own));
826
+ if (value && typeof value === "object") {
827
+ const out = {}, scoped = SKEY_IDX[own]?.[dir]?.[kind];
828
+ for (const [k, v] of Object.entries(value)) {
829
+ const hit = scoped?.get(k) ?? KEY_IDX[dir][kind].get(k);
830
+ if (hit === void 0 && o.strict && dir === "toPT" && PT_ONLY_KEYS.has(k) && !SAME_KEYS.has(k)) throw new PtKeyError(k);
831
+ const outKey = hit ?? k, ptKey = dir === "toEN" ? k : outKey;
832
+ out[outKey] = translate(v, o, own, ptKey);
833
+ }
834
+ return out;
835
+ }
836
+ if (typeof value === "string" && own !== null) {
837
+ const idx = SVAL_IDX[container]?.[own] ?? VAL_IDX[own], mapped = idx?.[dir]?.[kind]?.get(value);
838
+ if (mapped !== void 0) return mapped;
839
+ if (o.strict && dir === "toPT" && kind === "req" && PT_ONLY_VALUES[own]?.has(value) && !EN_ALSO.has(`${own}|${value}`)) throw new PtKeyError(own, value);
840
+ if (o.paths && PATH_KEYS.has(own)) return rewritePath(value, dir);
841
+ }
842
+ return value;
843
+ }
844
+ var toEN = (value, kind = "res", o = {}) => translate(value, { dir: "toEN", kind, ...o });
845
+ var toPT = (value, kind = "req", o = {}) => translate(value, { dir: "toPT", kind, ...o });
846
+ var ERROR_CODES = {
847
+ rota_inexistente: "route_not_found",
848
+ metodo_nao_permitido: "method_not_allowed",
849
+ erro_interno: "internal_error",
850
+ nao_autenticado: "unauthorized",
851
+ chave_invalida: "invalid_api_key",
852
+ chave_revogada: "revoked_api_key",
853
+ chave_expirada: "expired_api_key",
854
+ mfa_necessario: "mfa_required",
855
+ codigo_invalido: "invalid_code",
856
+ mfa_ja_ativo: "mfa_already_enabled",
857
+ mfa_nao_iniciado: "mfa_not_started",
858
+ mfa_inativo: "mfa_not_enabled",
859
+ sessao_atual: "current_session",
860
+ sessao_inexistente: "session_not_found",
861
+ confirmacao_invalida: "invalid_confirmation",
862
+ sessao_invalida: "invalid_session",
863
+ origem_invalida: "invalid_origin",
864
+ sem_permissao: "forbidden",
865
+ muitas_requisicoes: "rate_limited",
866
+ corpo_vazio: "empty_body",
867
+ json_invalido: "invalid_json",
868
+ corpo_grande: "body_too_large",
869
+ campo_invalido: "invalid_field",
870
+ pedido_invalido: "invalid_request",
871
+ falta_horario: "missing_schedule",
872
+ sem_horario: "missing_time",
873
+ horario_passado: "time_in_past",
874
+ sem_vagas: "no_slots",
875
+ sem_vaga_livre: "no_free_slot",
876
+ post_inexistente: "post_not_found",
877
+ destino_inexistente: "item_not_found",
878
+ acao_inexistente: "action_not_found",
879
+ canal_inexistente: "channel_not_found",
880
+ canal_nao_conectado: "channel_not_connected",
881
+ chave_inexistente: "api_key_not_found",
882
+ workspace_inexistente: "workspace_not_found",
883
+ oauth_requer_painel: "oauth_requires_panel",
884
+ video_publicado: "video_not_private",
885
+ canal_em_uso: "channel_in_use",
886
+ canal_conectado: "channel_connected",
887
+ canal_do_buffer: "channel_is_buffer",
888
+ credenciais_invalidas: "invalid_credentials",
889
+ senha_fraca: "weak_password",
890
+ link_invalido: "invalid_link",
891
+ conta_travada: "account_locked",
892
+ passkey_invalida: "invalid_passkey",
893
+ passkey_inexistente: "passkey_not_found",
894
+ passkey_existente: "passkey_exists",
895
+ desafio_invalido: "invalid_challenge",
896
+ limite_de_passkeys: "passkey_limit",
897
+ codigo_necessario: "code_required",
898
+ workspace_da_plataforma: "platform_workspace",
899
+ migracao_pendente: "migration_pending",
900
+ chave_em_uso: "api_key_in_use",
901
+ video_inexistente: "video_not_found",
902
+ sem_video: "no_video",
903
+ sem_avatar: "no_avatar",
904
+ midia_inexistente: "media_not_found",
905
+ midia_em_uso: "media_in_use",
906
+ midia_nao_pronta: "media_not_ready",
907
+ midia_tipo: "media_type_mismatch",
908
+ midia_travada: "media_locked",
909
+ rede_desconhecida: "unknown_network",
910
+ rede_nao_suportada: "network_not_supported",
911
+ estado_invalido: "invalid_state",
912
+ id_existente: "id_exists",
913
+ ja_adotado: "already_adopted",
914
+ nada_a_publicar: "nothing_to_publish",
915
+ nao_editavel: "not_editable",
916
+ post_adotado: "post_adopted",
917
+ post_preparado: "post_prepared",
918
+ sem_credencial_oauth: "oauth_not_configured",
919
+ tem_api: "followers_from_api",
920
+ youtube_nao_conectado: "youtube_not_connected",
921
+ instagram_meta_nao_conectado: "instagram_meta_not_connected",
922
+ falha_download: "download_failed",
923
+ mime_nao_suportado: "unsupported_mime",
924
+ // upload_media do MCP (docs/mcp.md)
925
+ arquivo_grande: "file_too_large",
926
+ parte_grande: "part_too_large",
927
+ parte_pequena: "part_too_small",
928
+ parte_vazia: "part_empty",
929
+ partes_desiguais: "parts_unequal",
930
+ partes_faltando: "parts_missing",
931
+ sem_partes: "no_parts",
932
+ tamanho_diferente: "size_mismatch",
933
+ r2_recusou: "storage_rejected",
934
+ identidade_inexistente: "identity_not_linked",
935
+ ultima_forma_de_login: "last_login_method",
936
+ credencial_oauth_ausente: "missing_oauth_credential",
937
+ ja_conectado: "already_connected",
938
+ oauth_state_invalido: "oauth_state_invalid",
939
+ oauth_negado: "oauth_denied",
940
+ sem_conta_instagram: "no_instagram_account",
941
+ varias_contas_instagram: "multiple_instagram_accounts",
942
+ troca_token_falhou: "token_exchange_failed",
943
+ permissao_publicar_ausente: "publish_permission_missing",
944
+ token_sem_acesso: "token_cannot_read_account",
945
+ webhook_inexistente: "webhook_not_found",
946
+ entrega_inexistente: "delivery_not_found",
947
+ url_insegura: "unsafe_url",
948
+ limite_webhooks: "webhook_limit_reached",
949
+ ideia_inexistente: "idea_not_found",
950
+ limite_ideias: "idea_limit_reached",
951
+ idempotencia_reutilizada: "idempotency_key_reuse",
952
+ idempotencia_em_andamento: "idempotency_in_progress",
953
+ idempotencia_resposta_grande: "idempotency_response_too_large",
954
+ buffer_erro: "buffer_error",
955
+ instagram_erro: "instagram_error",
956
+ tiktok_indisponivel: "tiktok_unavailable",
957
+ youtube_erro: "youtube_error"
958
+ };
959
+ var ERROR_CODES_REVERSE = Object.fromEntries(Object.entries(ERROR_CODES).map(([pt, en]) => [en, pt]));
960
+
961
+ // ../core/src/segredos.mjs
962
+ import { execFileSync } from "node:child_process";
963
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
964
+ import { homedir } from "node:os";
965
+ import { dirname, join } from "node:path";
966
+ var SERVICO = "praeko";
967
+ var SERVICO_ANTIGO = "arauto";
968
+ var REFS_CONHECIDAS = ["praeko/chave", "buffer/principal", "youtube/cliente", "youtube/token", "instagram/token", "instagram/meta", "tiktok/cliente", "tiktok/token", "tiktok/pendente"];
969
+ var refAntiga = (ref2) => ref2.startsWith("praeko/") ? "arauto/" + ref2.slice("praeko/".length) : ref2;
970
+ var sufixoEnv = (ref2) => ref2.toUpperCase().replace(/[^A-Z0-9]/g, "_");
971
+ var envDe = (ref2) => `PRAEKO_KEY_${sufixoEnv(ref2)}`;
972
+ var envAntigaDe = (ref2) => `ARAUTO_KEY_${sufixoEnv(refAntiga(ref2))}`;
973
+ var semChaveiro = (env = process.env) => !!(env.PRAEKO_SEM_CHAVEIRO || env.ARAUTO_SEM_CHAVEIRO);
974
+ var chaveiro = {
975
+ ativo: () => process.platform === "darwin" && !semChaveiro(),
976
+ ler(servico, ref2) {
977
+ try {
978
+ return execFileSync("security", ["find-generic-password", "-s", servico, "-a", ref2, "-w"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim() || null;
979
+ } catch {
980
+ return null;
981
+ }
982
+ },
983
+ gravar(servico, ref2, valor) {
984
+ execFileSync("security", ["add-generic-password", "-U", "-s", servico, "-a", ref2, "-w", valor], { stdio: ["ignore", "ignore", "pipe"] });
985
+ }
986
+ };
987
+ var arquivoSegredos = () => process.env.PRAEKO_SEGREDOS || process.env.ARAUTO_SEGREDOS || join(homedir(), ".config", "praeko", "segredos.json");
988
+ var arquivoSegredosAntigo = () => join(homedir(), ".config", "arauto", "segredos.json");
989
+ var lerArquivo = (f = arquivoSegredos()) => {
990
+ if (!existsSync(f)) return {};
991
+ try {
992
+ return JSON.parse(readFileSync(f, "utf8"));
993
+ } catch {
994
+ return {};
995
+ }
996
+ };
997
+ function obterNovo(ref2) {
998
+ if (process.env[envDe(ref2)]) return process.env[envDe(ref2)];
999
+ if (chaveiro.ativo()) {
1000
+ const k = chaveiro.ler(SERVICO, ref2);
1001
+ if (k) return k;
1002
+ }
1003
+ return lerArquivo()[ref2] ?? null;
1004
+ }
1005
+ function obterAntigoGuardado(ref2) {
1006
+ const velha = refAntiga(ref2);
1007
+ if (chaveiro.ativo()) {
1008
+ const k = chaveiro.ler(SERVICO_ANTIGO, velha);
1009
+ if (k) return k;
1010
+ }
1011
+ return lerArquivo(arquivoSegredos())[velha] ?? lerArquivo(arquivoSegredosAntigo())[velha] ?? null;
1012
+ }
1013
+ var obterAntigo = (ref2) => process.env[envAntigaDe(ref2)] || obterAntigoGuardado(ref2);
1014
+ var obter = (ref2) => obterNovo(ref2) ?? obterAntigo(ref2);
1015
+ function guardar(ref2, valor) {
1016
+ if (chaveiro.ativo()) {
1017
+ chaveiro.gravar(SERVICO, ref2, valor);
1018
+ return "chaveiro";
1019
+ }
1020
+ const f = arquivoSegredos();
1021
+ mkdirSync(dirname(f), { recursive: true });
1022
+ const s = lerArquivo();
1023
+ s[ref2] = valor;
1024
+ writeFileSync(f, JSON.stringify(s, null, 2) + "\n", { mode: 384 });
1025
+ chmodSync(f, 384);
1026
+ return "arquivo";
1027
+ }
1028
+ var segredos = { obter, guardar, obterNovo, obterAntigo, obterAntigoGuardado };
1029
+ function migrar(loja = segredos, refs = REFS_CONHECIDAS) {
1030
+ const r = { copiados: [], jaNoNovo: [], ausentes: [] };
1031
+ for (const ref2 of refs) {
1032
+ if (loja.obterNovo(ref2)) {
1033
+ r.jaNoNovo.push(ref2);
1034
+ continue;
1035
+ }
1036
+ const v = loja.obterAntigoGuardado(ref2);
1037
+ if (!v) {
1038
+ r.ausentes.push(ref2);
1039
+ continue;
1040
+ }
1041
+ loja.guardar(ref2, v);
1042
+ r.copiados.push(ref2);
1043
+ }
1044
+ return r;
1045
+ }
1046
+
1047
+ // ../core/src/adaptadores/remoto.mjs
1048
+ var REF_CHAVE = "praeko/chave";
1049
+ var itemDoContrato = (i) => ({ rede: i.rede, id: i.id, estado: i.estado, quando: i.quando, ...i.url ? { url: i.url } : {}, ...i.studio ? { studio: i.studio } : {}, ...i.aviso ? { aviso: i.aviso } : {}, ...i.custom ? { custom: true } : {} });
1050
+ function criar({ config, ctx, praeko, arauto } = {}) {
1051
+ const injetado = ctx?.praeko ?? praeko ?? ctx?.arauto ?? arauto, seg = ctx?.segredos || segredos, log = ctx?.log || (() => {
1052
+ });
1053
+ const cliente = () => {
1054
+ if (injetado) return injetado;
1055
+ const url = config?.remoto?.url, chave = process.env.PRAEKO_KEY || process.env.ARAUTO_CHAVE || seg.obter(REF_CHAVE);
1056
+ if (!url) throw new Error('falta a url do servi\xE7o: "remoto": { "url": "https://\u2026" } no praeko.json (ou --server)');
1057
+ if (!chave) throw new Error("falta a chave do servi\xE7o: defina PRAEKO_KEY (ou ARAUTO_CHAVE) ou rode `echo $KEY | praeko key store`");
1058
+ return new Praeko({ url, key: chave });
1059
+ };
1060
+ const novo = (a) => !!a.media;
1061
+ const subir = (a, caminho, tipo, onProgress) => uploadFile(a, caminho, { type: tipo, onProgress });
1062
+ return {
1063
+ nome: "remoto",
1064
+ async publicar(pedido, { aoProgredir } = {}) {
1065
+ const erros = validarPedido(pedido.midia?.videoId ? { ...pedido, midia: { video: pedido.midia.videoId } } : pedido);
1066
+ if (erros.length) throw new Error(`pedido inv\xE1lido: ${erros.join("; ")}`);
1067
+ const a = cliente();
1068
+ let videoId = pedido.midia.videoId, capaId = pedido.midia.capaId;
1069
+ if (!videoId) {
1070
+ log(` enviando o v\xEDdeo ao servi\xE7o (${pedido.midia.video})\u2026`);
1071
+ videoId = (await subir(a, pedido.midia.video, "video", aoProgredir)).id;
1072
+ }
1073
+ if (!capaId && pedido.midia.capa) {
1074
+ log(" enviando a capa\u2026");
1075
+ capaId = (await subir(a, pedido.midia.capa, "cover")).id;
1076
+ }
1077
+ const extras = /* @__PURE__ */ new Map(), doDestino = async (d) => {
1078
+ if (!d.midia) return void 0;
1079
+ const m = {};
1080
+ for (const [campo, tipo] of [["video", "video"], ["capa", "capa"]]) {
1081
+ const x = d.midia[campo], idJa = d.midia[`${campo}Id`];
1082
+ if (idJa) m[campo] = idJa;
1083
+ else if (x) {
1084
+ if (!extras.has(x)) {
1085
+ log(` enviando ${tipo} do destino ${d.rede} (${x})\u2026`);
1086
+ extras.set(x, (await subir(a, x, tipo === "capa" ? "cover" : tipo)).id);
1087
+ }
1088
+ m[campo] = extras.get(x);
1089
+ }
1090
+ }
1091
+ return Object.keys(m).length ? m : void 0;
1092
+ };
1093
+ const destinosCorpo = [];
1094
+ for (const d of pedido.destinos) {
1095
+ const m = await doDestino(d);
1096
+ destinosCorpo.push({ ...d, ...m ? { midia: m } : {} });
1097
+ }
1098
+ const corpo = { ...pedido.id ? { id: pedido.id } : {}, titulo: pedido.titulo, midia: { video: videoId, ...capaId ? { capa: capaId } : {} }, textos: pedido.textos, destinos: destinosCorpo.map((d) => ({ rede: d.rede, ...d.midia ? { midia: d.midia } : {}, ...d.quando ? { quando: d.quando } : {}, ...d.modo ? { modo: d.modo } : {}, ...d.fila ? { fila: true } : {}, ...d.rascunho ? { rascunho: true } : {} })), ...pedido.etiquetas ? { etiquetas: pedido.etiquetas } : {}, ...pedido.opcoes ? { opcoes: pedido.opcoes } : {}, ...pedido.fila ? { fila: true } : {}, ...pedido.rascunho ? { rascunho: true } : {}, origem: "arauto-cli" };
1099
+ const r = novo(a) ? toPT(await a.posts.create(toEN(corpo, "req")), "res") : await a.posts.criar(corpo);
1100
+ return { id: r.id, itens: r.itens.map(itemDoContrato) };
1101
+ },
1102
+ async status(id) {
1103
+ const c = cliente(), p = novo(c) ? toPT(await c.posts.get(id), "res") : await c.posts.obter(id);
1104
+ return { id: p.id, itens: p.itens.map(itemDoContrato) };
1105
+ },
1106
+ async cancelar(id) {
1107
+ const c = cliente(), r = novo(c) ? toPT(await c.posts.cancel(id), "res") : await c.posts.cancelar(id);
1108
+ return { cancelados: r.cancelados, mantidos: r.mantidos.map(itemDoContrato) };
1109
+ },
1110
+ async canais() {
1111
+ const c0 = cliente(), r0 = novo(c0) ? toPT(await c0.channels.list({ verify: true }), "res") : await c0.canais.listar({ verificar: true });
1112
+ return r0.canais.map((c) => ({ rede: c.rede, conectado: c.conectado, detalhe: c.detalhe, ...c.id ? { id: c.id } : {} }));
1113
+ }
1114
+ };
1115
+ }
1116
+
1117
+ export {
1118
+ Praeko,
1119
+ segredos,
1120
+ migrar,
1121
+ REF_CHAVE,
1122
+ itemDoContrato,
1123
+ criar,
1124
+ remoto_exports
1125
+ };