@thecodecodex/tools 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.
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/package.json +46 -4
- package/src/base64.ts +36 -0
- package/src/br/cnpj.ts +55 -0
- package/src/br/cpf.ts +61 -0
- package/src/cli/main.ts +93 -0
- package/src/cli/tools.ts +643 -0
- package/src/compensation-comparator.ts +124 -0
- package/src/css-playground.ts +699 -0
- package/src/dev-ref.ts +295 -0
- package/src/diff.ts +301 -0
- package/src/finance/clt.ts +189 -0
- package/src/finance/compound.ts +78 -0
- package/src/finance/pj.ts +146 -0
- package/src/fov.ts +145 -0
- package/src/hash.ts +13 -0
- package/src/image-to-pdf.ts +299 -0
- package/src/invoice-pdf.ts +134 -0
- package/src/invoice.ts +94 -0
- package/src/jwt.ts +44 -0
- package/src/market.ts +49 -0
- package/src/password.ts +51 -0
- package/src/pdf-studio.ts +453 -0
- package/src/random/index.ts +19 -0
- package/src/regex.ts +426 -0
- package/src/skills.json +60 -0
- package/src/skills.ts +52 -0
- package/src/state-holidays.ts +94 -0
- package/src/table-convert/code.ts +99 -0
- package/src/table-convert/index.ts +42 -0
- package/src/table-convert/model.ts +72 -0
- package/src/table-convert/parse.ts +242 -0
- package/src/table-convert/serialize.ts +435 -0
- package/src/table-convert/transform.ts +133 -0
- package/src/table-convert/xlsx.ts +262 -0
- package/src/table-convert/zip.ts +127 -0
- package/src/timestamp.ts +50 -0
- package/src/url-codec.ts +25 -0
- package/src/uuid.ts +53 -0
- package/src/vacation.ts +171 -0
package/src/cli/tools.ts
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
// Comandos do script de terminal (thecodecodex.com/cli.js). Cada comando aceita os MESMOS parâmetros da URL
|
|
2
|
+
// da ferramenta no site e chama as mesmas funções desta lib — o link do site vira o comando do terminal.
|
|
3
|
+
import { cnpjKind, formatCnpj, generateCnpj, isValidCnpj, type CnpjKind } from "../br/cnpj"
|
|
4
|
+
import { CPF_REGIONS, cpfRegion, formatCpf, generateCpf, isValidCpf } from "../br/cpf"
|
|
5
|
+
import { cltYear } from "../finance/clt"
|
|
6
|
+
import { simulate } from "../finance/compound"
|
|
7
|
+
import { bestRegime, compareRegimes } from "../finance/pj"
|
|
8
|
+
import { invoiceCodec, EMPTY_INVOICE, totals } from "../invoice"
|
|
9
|
+
import { invoicePdf } from "../invoice-pdf"
|
|
10
|
+
import { cdiAnnual, fxRate, ipca12m, market, marketFetchedAt, savingsAnnual, type Currency } from "../market"
|
|
11
|
+
import { UFS, type Uf } from "../state-holidays"
|
|
12
|
+
import { codec, type UrlCodec } from "../url-codec"
|
|
13
|
+
import { holidaysFor, planSplit, SPLITS, suggest, type Schedule } from "../vacation"
|
|
14
|
+
import { diffTexts, unifiedPatch, type Granularity, type JsonMode } from "../diff"
|
|
15
|
+
import { convert, type Codec, type Direction } from "../base64"
|
|
16
|
+
import { CSS_WARNING, DEMO_IDS, demoCss, DEMOS, findDemo, normalizeValues, previewDocument, variablesCss } from "../css-playground"
|
|
17
|
+
import { annualTargetFromCltOffer, DEFAULT_OPTIONS, foreignAmount, generateComparison } from "../compensation-comparator"
|
|
18
|
+
import { HTTP_CLASSES, queryDevRef, REF_SECTIONS, type HttpClass, type RefSection } from "../dev-ref"
|
|
19
|
+
import { calculateAngles, DEFAULT_SETUP, gameSettings, type Setup } from "../fov"
|
|
20
|
+
import { hashAll } from "../hash"
|
|
21
|
+
import { BACKGROUNDS, createSampleImages, DEFAULT_IMAGE_PDF_OPTIONS, FIT_MODES, imagesToPdf, ORIENTATIONS, PAGE_SIZES, type FitMode, type Orientation, type PageBackground, type PageSize } from "../image-to-pdf"
|
|
22
|
+
import { ALIGNS, ASCII_STYLES, CASES, convertTable, DECIMALS, DELIMITER_NAMES, INDENTS, INPUT_FORMATS, JSON_SHAPES, OUTPUT_FORMATS, SQL_DIALECTS } from "../table-convert"
|
|
23
|
+
import { decodeJwt, isExpired } from "../jwt"
|
|
24
|
+
import { entropyBits, generatePassword, strength, type Charset } from "../password"
|
|
25
|
+
import {
|
|
26
|
+
createSamplePdf,
|
|
27
|
+
DEFAULT_STUDIO_OPTIONS,
|
|
28
|
+
NUMBER_FORMATS,
|
|
29
|
+
NUMBER_POSITIONS,
|
|
30
|
+
processPdf,
|
|
31
|
+
ROTATIONS,
|
|
32
|
+
TABS,
|
|
33
|
+
WATERMARK_COLORS,
|
|
34
|
+
type NumberFormat,
|
|
35
|
+
type NumberPosition,
|
|
36
|
+
type PdfStudioTab,
|
|
37
|
+
type Rotation,
|
|
38
|
+
type WatermarkColor,
|
|
39
|
+
} from "../pdf-studio"
|
|
40
|
+
import { DEFAULT_SKILLS_DIR, findSkill, installPlan, skillFile, SKILLS } from "../skills"
|
|
41
|
+
import { evaluateRegex, REGEX_MODES, REGEX_PRESETS, type RegexMode } from "../regex"
|
|
42
|
+
import { parseTimestamp, toUnix } from "../timestamp"
|
|
43
|
+
import { formatUuid, inspectUuid, uuidV4, uuidV7 } from "../uuid"
|
|
44
|
+
|
|
45
|
+
export type Params = URLSearchParams
|
|
46
|
+
|
|
47
|
+
export interface ParamDoc {
|
|
48
|
+
name: string
|
|
49
|
+
default: string
|
|
50
|
+
description: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// arquivos binários (PDF, .xlsx) saem como bytes no stdout; text sai cru (ex.: um SKILL.md);
|
|
54
|
+
// write lista arquivos que o script grava no disco (instalação de skills), com um resumo em JSON
|
|
55
|
+
export type ToolOutput = { json: unknown } | { pdf: Uint8Array, filename: string } | { file: Uint8Array, filename: string } | { text: string } | { write: { path: string, content: string }[], json: unknown }
|
|
56
|
+
|
|
57
|
+
export interface CliTool {
|
|
58
|
+
description: string
|
|
59
|
+
params: ParamDoc[]
|
|
60
|
+
run: (params: Params) => ToolOutput | Promise<ToolOutput>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Lê um parâmetro com o mesmo conversor do site; valor inválido ou ausente → padrão
|
|
64
|
+
function read<T>(params: Params, name: string, fallback: T, urlCodec: UrlCodec<T>): T {
|
|
65
|
+
const raw = params.get(name)
|
|
66
|
+
if (raw === null) return fallback
|
|
67
|
+
const value = urlCodec.parse(raw)
|
|
68
|
+
if (value === undefined) throw new Error(`Parâmetro inválido: ${name}=${raw}`)
|
|
69
|
+
return value
|
|
70
|
+
}
|
|
71
|
+
const num = (p: Params, name: string, fallback: number) => read(p, name, fallback, codec.number)
|
|
72
|
+
const bool = (p: Params, name: string, fallback: boolean) => read(p, name, fallback, codec.boolean)
|
|
73
|
+
const str = (p: Params, name: string, fallback = "") => read(p, name, fallback, codec.string)
|
|
74
|
+
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, Math.round(value)))
|
|
75
|
+
// booleanos documentados como na URL: 1 / 0
|
|
76
|
+
const doc = (name: string, fallback: string | number | boolean, description: string): ParamDoc => ({ name, default: typeof fallback === "boolean" ? (fallback ? "1" : "0") : String(fallback), description })
|
|
77
|
+
const marketSnapshot = (keys: (keyof typeof market)[]) => ({ fetchedAt: marketFetchedAt, ...Object.fromEntries(keys.map((k) => [k, market[k]])) })
|
|
78
|
+
|
|
79
|
+
export const TOOLS: Record<string, CliTool> = {
|
|
80
|
+
cpf: {
|
|
81
|
+
description: "Gera CPFs válidos para testes e/ou valida um CPF",
|
|
82
|
+
params: [doc("q", 1, "quantidade (1-50)"), doc("uf", "", "estado de emissão"), doc("mask", true, "1 = com máscara"), doc("v", "", "CPF para validar")],
|
|
83
|
+
run: (p) => {
|
|
84
|
+
const uf = read(p, "uf", "", codec.oneOf(["", ...Object.keys(CPF_REGIONS)]))
|
|
85
|
+
const mask = bool(p, "mask", true)
|
|
86
|
+
const generated = Array.from({ length: clamp(num(p, "q", 1), 1, 50) }, () => generateCpf({ uf: uf || undefined })).map((v) => (mask ? formatCpf(v) : v))
|
|
87
|
+
const input = str(p, "v")
|
|
88
|
+
return { json: { generated, ...(input ? { validation: { input, valid: isValidCpf(input), formatted: formatCpf(input), regions: isValidCpf(input) ? cpfRegion(input) : [] } } : {}) } }
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
cnpj: {
|
|
92
|
+
description: "Gera CNPJs (numérico ou alfanumérico) e/ou valida um CNPJ",
|
|
93
|
+
params: [doc("q", 1, "quantidade (1-50)"), doc("kind", "numeric", "numeric | alphanumeric"), doc("mask", true, "1 = com máscara"), doc("v", "", "CNPJ para validar")],
|
|
94
|
+
run: (p) => {
|
|
95
|
+
const kind = read<CnpjKind>(p, "kind", "numeric", codec.oneOf(["numeric", "alphanumeric"] as const))
|
|
96
|
+
const mask = bool(p, "mask", true)
|
|
97
|
+
const generated = Array.from({ length: clamp(num(p, "q", 1), 1, 50) }, () => generateCnpj({ kind })).map((v) => (mask ? formatCnpj(v) : v))
|
|
98
|
+
const input = str(p, "v")
|
|
99
|
+
return { json: { generated, ...(input ? { validation: { input, valid: isValidCnpj(input), formatted: formatCnpj(input), kind: cnpjKind(input) } } : {}) } }
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
password: {
|
|
103
|
+
description: "Gera senhas com crypto.getRandomValues",
|
|
104
|
+
params: [doc("len", 20, "tamanho (4-128)"), doc("sets", "lower,upper,digits,symbols", "conjuntos de caracteres"), doc("noamb", false, "1 = evita I, l, 1, O, 0"), doc("q", 5, "quantidade (1-50)")],
|
|
105
|
+
run: (p) => {
|
|
106
|
+
const SETS: Charset[] = ["lower", "upper", "digits", "symbols"]
|
|
107
|
+
const options = { length: clamp(num(p, "len", 20), 4, 128), sets: read<Charset[]>(p, "sets", SETS, codec.list(codec.oneOf(SETS))), excludeAmbiguous: bool(p, "noamb", false) }
|
|
108
|
+
const bits = entropyBits(options)
|
|
109
|
+
return { json: { passwords: Array.from({ length: clamp(num(p, "q", 5), 1, 50) }, () => generatePassword(options)), entropyBits: bits, strength: strength(bits) } }
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
uuid: {
|
|
113
|
+
description: "Gera UUID v4/v7 e/ou inspeciona um UUID",
|
|
114
|
+
params: [doc("ver", 4, "4 | 7"), doc("q", 5, "quantidade (1-100)"), doc("upper", false, "1 = maiúsculas"), doc("hyphens", true, "0 = sem hífens"), doc("v", "", "UUID para inspecionar")],
|
|
115
|
+
run: (p) => {
|
|
116
|
+
const version = read(p, "ver", 4, codec.oneOf([4, 7] as const))
|
|
117
|
+
const format = { uppercase: bool(p, "upper", false), hyphens: bool(p, "hyphens", true) }
|
|
118
|
+
const uuids = Array.from({ length: clamp(num(p, "q", 5), 1, 100) }, () => formatUuid(version === 7 ? uuidV7() : uuidV4(), format))
|
|
119
|
+
const input = str(p, "v")
|
|
120
|
+
const info = input ? inspectUuid(input) : undefined
|
|
121
|
+
return { json: { uuids, ...(info ? { inspect: { input, ...info, timestamp: info.timestamp?.toISOString() } } : {}) } }
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
hash: {
|
|
125
|
+
description: "SHA-1/256/384/512 de um texto",
|
|
126
|
+
params: [doc("text", "", "texto")],
|
|
127
|
+
run: async (p) => ({ json: { text: str(p, "text"), hashes: await hashAll(str(p, "text")) } }),
|
|
128
|
+
},
|
|
129
|
+
base64: {
|
|
130
|
+
description: "Codifica/decodifica Base64, Base64URL ou URL",
|
|
131
|
+
params: [doc("text", "", "texto"), doc("op", "encode", "encode | decode"), doc("fmt", "base64", "base64 | base64url | url")],
|
|
132
|
+
run: (p) => {
|
|
133
|
+
const format = read<Codec>(p, "fmt", "base64", codec.oneOf(["base64", "base64url", "url"] as const))
|
|
134
|
+
const op = read<Direction>(p, "op", "encode", codec.oneOf(["encode", "decode"] as const))
|
|
135
|
+
const result = convert(str(p, "text"), format, op)
|
|
136
|
+
if (!result.ok) throw new Error("Entrada inválida para decodificar neste formato")
|
|
137
|
+
return { json: { op, format, output: result.value } }
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
jwt: {
|
|
141
|
+
description: "Decodifica um JWT (sem verificar a assinatura)",
|
|
142
|
+
params: [doc("token", "", "token JWT")],
|
|
143
|
+
run: (p) => {
|
|
144
|
+
const result = decodeJwt(str(p, "token"))
|
|
145
|
+
if (!result.ok) throw new Error(result.error === "format" ? "JWT precisa de três partes separadas por ponto" : "Header ou payload não são JSON válidos")
|
|
146
|
+
const { jwt } = result
|
|
147
|
+
return { json: { header: jwt.header, payload: jwt.payload, issuedAt: jwt.issuedAt?.toISOString(), expiresAt: jwt.expiresAt?.toISOString(), notBefore: jwt.notBefore?.toISOString(), expired: isExpired(jwt), signatureVerified: false } }
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
timestamp: {
|
|
151
|
+
description: "Converte timestamp Unix ⇄ data",
|
|
152
|
+
params: [doc("t", "agora", "timestamp (s, ms, µs ou ns)"), doc("date", "", "data ISO para converter em timestamp")],
|
|
153
|
+
run: (p) => {
|
|
154
|
+
const raw = str(p, "t") || String(toUnix(new Date()).seconds)
|
|
155
|
+
const parsed = parseTimestamp(raw)
|
|
156
|
+
if (!parsed) throw new Error(`Timestamp inválido: ${raw}`)
|
|
157
|
+
const date = str(p, "date")
|
|
158
|
+
const fromDate = date ? new Date(date) : undefined
|
|
159
|
+
return {
|
|
160
|
+
json: {
|
|
161
|
+
fromUnix: { input: raw, unit: parsed.unit, iso: parsed.date.toISOString(), utc: parsed.date.toUTCString() },
|
|
162
|
+
...(fromDate && !Number.isNaN(fromDate.getTime()) ? { fromDate: { input: date, ...toUnix(fromDate), iso: fromDate.toISOString() } } : {}),
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
"fov-calculator": {
|
|
168
|
+
description: "FOV para simuladores de corrida a partir do seu setup",
|
|
169
|
+
params: [doc("ar", "16x9", "proporção"), doc("in", 27, "diagonal (pol.)"), doc("d", 60, "distância (cm)"), doc("n", 1, "telas: 1 | 3"), doc("b", 0, "moldura (mm)"), doc("c", false, "1 = curva"), doc("r", 1800, "raio (mm)"), doc("game", "", "filtra jogos pelo nome")],
|
|
170
|
+
run: (p) => {
|
|
171
|
+
const [x = 16, y = 9] = (str(p, "ar") || "16x9").split("x").map(Number)
|
|
172
|
+
const setup: Setup = {
|
|
173
|
+
...DEFAULT_SETUP,
|
|
174
|
+
aspect: [x, y],
|
|
175
|
+
diagonalIn: num(p, "in", DEFAULT_SETUP.diagonalIn),
|
|
176
|
+
distanceCm: num(p, "d", DEFAULT_SETUP.distanceCm),
|
|
177
|
+
screens: num(p, "n", 1) === 3 ? 3 : 1,
|
|
178
|
+
bezelMm: num(p, "b", 0),
|
|
179
|
+
curved: bool(p, "c", false),
|
|
180
|
+
radiusMm: num(p, "r", DEFAULT_SETUP.radiusMm),
|
|
181
|
+
}
|
|
182
|
+
const angles = calculateAngles(setup)
|
|
183
|
+
const filter = str(p, "game").toLowerCase()
|
|
184
|
+
return { json: { setup, angles, games: gameSettings(setup, angles).filter((g) => g.name.toLowerCase().includes(filter)) } }
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
"net-salary": {
|
|
188
|
+
description: "Salário líquido CLT 2026 e pacote anual",
|
|
189
|
+
params: [
|
|
190
|
+
doc("bruto", 8000, "salário bruto"),
|
|
191
|
+
doc("dependentes", 0, "dependentes no IR"),
|
|
192
|
+
doc("pensao", 0, "pensão alimentícia"),
|
|
193
|
+
doc("vt", 0, "custo do vale-transporte"),
|
|
194
|
+
doc("descontos", 0, "outros descontos"),
|
|
195
|
+
doc("beneficios", 0, "benefícios mensais"),
|
|
196
|
+
doc("bonus", 0, "PLR/bônus líquido anual"),
|
|
197
|
+
doc("fgts", true, "0 = sem FGTS no pacote"),
|
|
198
|
+
],
|
|
199
|
+
run: (p) => {
|
|
200
|
+
const input = {
|
|
201
|
+
gross: num(p, "bruto", 8000),
|
|
202
|
+
dependents: clamp(num(p, "dependentes", 0), 0, 99),
|
|
203
|
+
alimony: num(p, "pensao", 0),
|
|
204
|
+
transportVoucher: num(p, "vt", 0),
|
|
205
|
+
otherDiscounts: num(p, "descontos", 0),
|
|
206
|
+
monthlyBenefits: num(p, "beneficios", 0),
|
|
207
|
+
annualBonus: num(p, "bonus", 0),
|
|
208
|
+
includeFgts: bool(p, "fgts", true),
|
|
209
|
+
}
|
|
210
|
+
return { json: { input, result: cltYear(input.gross, input) } }
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
"compensation-comparator": {
|
|
214
|
+
description: "Compara CLT, PJ e Cooperado (meta anual ou proposta CLT)",
|
|
215
|
+
params: [
|
|
216
|
+
doc("modo", "meta", "meta | clt"),
|
|
217
|
+
doc("meta", DEFAULT_OPTIONS.targetLiquidity, "meta de liquidez anual"),
|
|
218
|
+
doc("clt", 8000, "bruto da proposta CLT (modo=clt)"),
|
|
219
|
+
doc("pj", DEFAULT_OPTIONS.pjTaxRate, "alíquota PJ (fração)"),
|
|
220
|
+
doc("taxa", DEFAULT_OPTIONS.coopAdminFeeRate, "taxa admin cooperativa (fração)"),
|
|
221
|
+
doc("ferias", DEFAULT_OPTIONS.businessVacationDays, "dias de férias do cooperado"),
|
|
222
|
+
doc("prolabore", DEFAULT_OPTIONS.proLaboreValue, "remuneração declarada do cooperado"),
|
|
223
|
+
doc("beneficios", 0, "benefícios CLT mensais"),
|
|
224
|
+
doc("bonus", 0, "PLR/bônus CLT anual"),
|
|
225
|
+
doc("fgts", true, "0 = sem FGTS"),
|
|
226
|
+
doc("moeda", "BRL", "BRL | USD | EUR | GBP | CAD"),
|
|
227
|
+
doc("cambio", "PTAX do dia", "câmbio"),
|
|
228
|
+
doc("spread", 0.01, "spread (fração)"),
|
|
229
|
+
],
|
|
230
|
+
run: (p) => {
|
|
231
|
+
const options = {
|
|
232
|
+
targetLiquidity: num(p, "meta", DEFAULT_OPTIONS.targetLiquidity),
|
|
233
|
+
pjTaxRate: num(p, "pj", DEFAULT_OPTIONS.pjTaxRate),
|
|
234
|
+
coopAdminFeeRate: num(p, "taxa", DEFAULT_OPTIONS.coopAdminFeeRate),
|
|
235
|
+
businessVacationDays: num(p, "ferias", DEFAULT_OPTIONS.businessVacationDays),
|
|
236
|
+
proLaboreValue: num(p, "prolabore", DEFAULT_OPTIONS.proLaboreValue),
|
|
237
|
+
cltBenefitsMonthly: num(p, "beneficios", 0),
|
|
238
|
+
cltAnnualBonus: num(p, "bonus", 0),
|
|
239
|
+
includeFgts: bool(p, "fgts", true),
|
|
240
|
+
}
|
|
241
|
+
const mode = read(p, "modo", "meta", codec.oneOf(["meta", "clt"] as const))
|
|
242
|
+
const offer = num(p, "clt", 8000)
|
|
243
|
+
const target = mode === "clt" ? annualTargetFromCltOffer(offer, options) : options.targetLiquidity
|
|
244
|
+
const results = generateComparison({ ...options, targetLiquidity: target })
|
|
245
|
+
const currency = read(p, "moeda", "BRL", codec.oneOf(["BRL", "USD", "EUR", "GBP", "CAD"] as const))
|
|
246
|
+
const rate = currency === "BRL" ? 1 : num(p, "cambio", fxRate(currency as Currency))
|
|
247
|
+
const spread = num(p, "spread", 0.01)
|
|
248
|
+
const pj = results[1]
|
|
249
|
+
return {
|
|
250
|
+
json: {
|
|
251
|
+
mode,
|
|
252
|
+
annualTarget: target,
|
|
253
|
+
results,
|
|
254
|
+
...(mode === "clt" && pj ? { breakeven: { cltGross: offer, pjRevenue: pj.monthlyGrossOrRevenue, aboveCltGross: (pj.monthlyGrossOrRevenue - offer) / offer } } : {}),
|
|
255
|
+
...(currency !== "BRL" && pj ? { foreign: { currency, exchangeRate: rate, spread, pjRevenue: foreignAmount(pj.monthlyGrossOrRevenue, rate, spread) } } : {}),
|
|
256
|
+
...(currency !== "BRL" ? { market: marketSnapshot([currency.toLowerCase() as keyof typeof market]) } : {}),
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
"compound-interest": {
|
|
262
|
+
description: "Juros compostos com IR regressivo, inflação, poupança e CDI",
|
|
263
|
+
params: [
|
|
264
|
+
doc("inicial", 10_000, "depósito inicial"),
|
|
265
|
+
doc("aporte", 1000, "aporte mensal"),
|
|
266
|
+
doc("anos", 10, "prazo em anos"),
|
|
267
|
+
doc("taxa", 0.12, "rentabilidade anual efetiva (fração)"),
|
|
268
|
+
doc("ir", true, "0 = sem IR"),
|
|
269
|
+
doc("ipca", "IPCA 12m do BCB", "inflação anual (fração)"),
|
|
270
|
+
doc("poupanca", "poupança do BCB", "poupança anual (fração)"),
|
|
271
|
+
doc("cdi", "CDI do BCB", "CDI anual (fração)"),
|
|
272
|
+
],
|
|
273
|
+
run: (p) => {
|
|
274
|
+
const base = { initial: num(p, "inicial", 10_000), monthly: num(p, "aporte", 1000), months: Math.max(1, Math.round(num(p, "anos", 10) * 12)), annualInflation: num(p, "ipca", ipca12m()) }
|
|
275
|
+
return {
|
|
276
|
+
json: {
|
|
277
|
+
input: base,
|
|
278
|
+
yours: simulate({ ...base, annualRate: num(p, "taxa", 0.12), taxed: bool(p, "ir", true) }),
|
|
279
|
+
savings: simulate({ ...base, annualRate: num(p, "poupanca", savingsAnnual()), taxed: false }),
|
|
280
|
+
cdi: simulate({ ...base, annualRate: num(p, "cdi", cdiAnnual()), taxed: true }),
|
|
281
|
+
market: marketSnapshot(["cdi", "ipca12m", "savingsMonthly"]),
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
"pj-taxes": {
|
|
287
|
+
description: "Impostos PJ: MEI, Simples III (Fator R), Simples V e Lucro Presumido",
|
|
288
|
+
params: [doc("faturamento", 20_000, "faturamento mensal"), doc("prolabore", 1621, "pró-labore mínimo"), doc("iss", 0.02, "ISS (fração)"), doc("contador", 300, "contador"), doc("despesas", 0, "outras despesas")],
|
|
289
|
+
run: (p) => {
|
|
290
|
+
const input = { monthlyRevenue: num(p, "faturamento", 20_000), proLabore: num(p, "prolabore", 1621), issRate: num(p, "iss", 0.02), accountant: num(p, "contador", 300), otherExpenses: num(p, "despesas", 0) }
|
|
291
|
+
const regimes = compareRegimes(input)
|
|
292
|
+
return { json: { input, best: bestRegime(regimes)?.regime, regimes } }
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
"vacation-planner": {
|
|
296
|
+
description: "Melhores datas de férias emendando feriados (CLT)",
|
|
297
|
+
params: [
|
|
298
|
+
doc("ano", "ano atual", "ano"),
|
|
299
|
+
doc("dias", 10, "dias de férias (modo=single)"),
|
|
300
|
+
doc("modo", "single", "single | split"),
|
|
301
|
+
doc("divisao", 1, `índice da divisão: ${SPLITS.map((s, i) => `${i}=${s.join("+")}`).join(", ")}`),
|
|
302
|
+
doc("escala", "5x2", "5x2 | 6x1"),
|
|
303
|
+
doc("facultativos", false, "1 = folga no Carnaval/Corpus Christi"),
|
|
304
|
+
doc("uf", "", "estado (feriados estaduais)"),
|
|
305
|
+
doc("folgas", "", "feriados locais: AAAA-MM-DD,..."),
|
|
306
|
+
],
|
|
307
|
+
run: (p) => {
|
|
308
|
+
const input = {
|
|
309
|
+
year: num(p, "ano", new Date().getFullYear()),
|
|
310
|
+
schedule: read<Schedule>(p, "escala", "5x2", codec.oneOf(["5x2", "6x1"] as const)),
|
|
311
|
+
includeOptional: bool(p, "facultativos", false),
|
|
312
|
+
extraDaysOff: read<string[]>(p, "folgas", [], codec.list(codec.string)),
|
|
313
|
+
uf: (read(p, "uf", "", codec.oneOf(["", ...UFS] as const)) || undefined) as Uf | undefined,
|
|
314
|
+
}
|
|
315
|
+
const mode = read(p, "modo", "single", codec.oneOf(["single", "split"] as const))
|
|
316
|
+
const holidays = holidaysFor(input)
|
|
317
|
+
if (mode === "split") {
|
|
318
|
+
const parts = SPLITS[clamp(num(p, "divisao", 1), 0, SPLITS.length - 1)] ?? [15, 15]
|
|
319
|
+
return { json: { input: { ...input, mode, split: parts }, plan: planSplit(input, parts), holidays } }
|
|
320
|
+
}
|
|
321
|
+
const days = clamp(num(p, "dias", 10), 1, 30)
|
|
322
|
+
return { json: { input: { ...input, mode, days }, suggestions: suggest({ ...input, days }, 5), holidays } }
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
diff: {
|
|
326
|
+
description: "Compara dois textos: linhas/caracteres diferentes, similaridade, JSON com chaves ordenadas e patch .diff",
|
|
327
|
+
params: [
|
|
328
|
+
doc("a", "", "texto original"),
|
|
329
|
+
doc("b", "", "texto alterado"),
|
|
330
|
+
doc("gran", "char", "destaque dentro da linha: char | word"),
|
|
331
|
+
doc("json", "off", "off | format | sortKeys"),
|
|
332
|
+
doc("ws", false, "1 = ignora espaços extras"),
|
|
333
|
+
doc("case", false, "1 = ignora maiúsculas"),
|
|
334
|
+
doc("eol", true, "0 = diferencia CRLF de LF"),
|
|
335
|
+
doc("sort", false, "1 = ordena as linhas antes"),
|
|
336
|
+
],
|
|
337
|
+
run: (p) => {
|
|
338
|
+
const result = diffTexts(str(p, "a"), str(p, "b"), {
|
|
339
|
+
granularity: read<Granularity>(p, "gran", "char", codec.oneOf(["char", "word"] as const)),
|
|
340
|
+
json: read<JsonMode>(p, "json", "off", codec.oneOf(["off", "format", "sortKeys"] as const)),
|
|
341
|
+
ignoreWhitespace: bool(p, "ws", false),
|
|
342
|
+
ignoreCase: bool(p, "case", false),
|
|
343
|
+
ignoreLineEndings: bool(p, "eol", true),
|
|
344
|
+
sortLines: bool(p, "sort", false),
|
|
345
|
+
})
|
|
346
|
+
return { json: { identical: result.identical, onlyOrderDiffers: result.onlyOrderDiffers, stats: result.stats, rows: result.rows.filter((r) => r.type !== "equal"), patch: unifiedPatch(result) } }
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
"css-playground": {
|
|
350
|
+
description: "Demos do CSS Playground: variáveis dos controles, CSS da demo e o HTML completo da prévia",
|
|
351
|
+
params: [
|
|
352
|
+
doc("demo", DEMO_IDS[0] ?? "", `demo: ${DEMO_IDS.join(" | ")}`),
|
|
353
|
+
doc("v", "", "valores dos controles, na ordem, separados por vírgula"),
|
|
354
|
+
doc("css", "", "CSS editado (no site fica depois do #)"),
|
|
355
|
+
doc("lang", "pt", "pt | en (comentários e textos da demo)"),
|
|
356
|
+
],
|
|
357
|
+
run: (p) => {
|
|
358
|
+
const demoId = read(p, "demo", DEMO_IDS[0] ?? "", codec.oneOf(DEMO_IDS))
|
|
359
|
+
const lang = read(p, "lang", "pt", codec.oneOf(["pt", "en"] as const))
|
|
360
|
+
const demo = findDemo(demoId)
|
|
361
|
+
const values = normalizeValues(demo, p.get("v") ? read(p, "v", [], codec.list(codec.string)) : [])
|
|
362
|
+
const css = str(p, "css") || demoCss(demo, lang)
|
|
363
|
+
const warning = CSS_WARNING[lang]
|
|
364
|
+
return {
|
|
365
|
+
json: {
|
|
366
|
+
demo: demo.id,
|
|
367
|
+
category: demo.category,
|
|
368
|
+
support: demo.support,
|
|
369
|
+
demos: DEMOS.length,
|
|
370
|
+
controls: demo.controls.map((control, i) => ({ name: control.name, kind: control.kind, value: values[i] })),
|
|
371
|
+
variables: variablesCss(demo, values),
|
|
372
|
+
css,
|
|
373
|
+
html: previewDocument({ demo, css, values, lang, warning }),
|
|
374
|
+
},
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
"table-convert": {
|
|
379
|
+
description: "Converte tabelas entre CSV/TSV, JSON, Markdown e HTML e 22 saídas (Excel, SQL, YAML, TOML, wikis, Python, TypeScript, Go…), com ordenação, colunas e limpeza",
|
|
380
|
+
params: [
|
|
381
|
+
doc("in", "", "tabela de entrada (texto)"),
|
|
382
|
+
doc("from", "auto", INPUT_FORMATS.join(" | ")),
|
|
383
|
+
doc("delim", "auto", `separador da entrada: auto | ${DELIMITER_NAMES.join(" | ")}`),
|
|
384
|
+
doc("header", true, "0 = a primeira linha é dado, não cabeçalho"),
|
|
385
|
+
doc("dec", "dot", "números na entrada: dot (1234.5) | comma (1.234,5)"),
|
|
386
|
+
doc("to", "json", `${OUTPUT_FORMATS.join(" | ")} (xlsx sai como arquivo: redirecione com > tabela.xlsx)`),
|
|
387
|
+
doc("types", true, "0 = tudo como texto (JSON, YAML, SQL)"),
|
|
388
|
+
doc("null", false, "1 = células vazias viram null"),
|
|
389
|
+
doc("odelim", "comma", `separador da saída CSV: ${DELIMITER_NAMES.join(" | ")}`),
|
|
390
|
+
doc("shape", "objects", `estrutura do JSON: ${JSON_SHAPES.join(" | ")}`),
|
|
391
|
+
doc("indent", "2", INDENTS.join(" | ")),
|
|
392
|
+
doc("tbl", "my_table", "nome da tabela SQL"),
|
|
393
|
+
doc("dialect", "mysql", SQL_DIALECTS.join(" | ")),
|
|
394
|
+
doc("multi", true, "0 = um INSERT por linha"),
|
|
395
|
+
doc("create", false, "1 = inclui CREATE TABLE"),
|
|
396
|
+
doc("align", "none", `Markdown/LaTeX: ${ALIGNS.join(" | ")}`),
|
|
397
|
+
doc("pretty", true, "0 = sem alinhar colunas / indentar"),
|
|
398
|
+
doc("root", "rows", "elemento raiz do XML / nome da lista no TOML"),
|
|
399
|
+
doc("rowtag", "row", "elemento de cada linha do XML"),
|
|
400
|
+
doc("box", "unicode", ASCII_STYLES.join(" | ")),
|
|
401
|
+
doc("type", "Row", "nome do tipo (Go, TypeScript)"),
|
|
402
|
+
doc("sheet", "Sheet1", "nome da aba (xlsx)"),
|
|
403
|
+
doc("trim", false, "1 = remove espaços extras"),
|
|
404
|
+
doc("empty", false, "1 = remove linhas e colunas vazias"),
|
|
405
|
+
doc("dedupe", false, "1 = remove linhas duplicadas"),
|
|
406
|
+
doc("transpose", false, "1 = transpõe"),
|
|
407
|
+
doc("case", "none", CASES.join(" | ")),
|
|
408
|
+
doc("find", "", "texto a localizar"),
|
|
409
|
+
doc("repl", "", "substituição"),
|
|
410
|
+
doc("regex", false, "1 = find é expressão regular"),
|
|
411
|
+
doc("icase", false, "1 = ignora maiúsculas ao localizar"),
|
|
412
|
+
doc("sort", -1, "índice (0, 1…) da coluna para ordenar"),
|
|
413
|
+
doc("desc", false, "1 = ordem decrescente"),
|
|
414
|
+
doc("cols", "", "colunas na ordem desejada, por índice: 2,0,1"),
|
|
415
|
+
],
|
|
416
|
+
run: (p) => {
|
|
417
|
+
const to = read(p, "to", "json", codec.oneOf(OUTPUT_FORMATS))
|
|
418
|
+
const { parsed, result, output, file } = convertTable(str(p, "in"), {
|
|
419
|
+
from: read(p, "from", "auto", codec.oneOf(INPUT_FORMATS)),
|
|
420
|
+
delimiter: read(p, "delim", "auto", codec.oneOf(["auto", ...DELIMITER_NAMES] as const)),
|
|
421
|
+
header: bool(p, "header", true),
|
|
422
|
+
decimal: read(p, "dec", "dot", codec.oneOf(DECIMALS)),
|
|
423
|
+
to,
|
|
424
|
+
types: bool(p, "types", true),
|
|
425
|
+
emptyAsNull: bool(p, "null", false),
|
|
426
|
+
outputDelimiter: read(p, "odelim", "comma", codec.oneOf(DELIMITER_NAMES)),
|
|
427
|
+
jsonShape: read(p, "shape", "objects", codec.oneOf(JSON_SHAPES)),
|
|
428
|
+
indent: read(p, "indent", "2", codec.oneOf(INDENTS)),
|
|
429
|
+
tableName: str(p, "tbl", "my_table"),
|
|
430
|
+
dialect: read(p, "dialect", "mysql", codec.oneOf(SQL_DIALECTS)),
|
|
431
|
+
multiRow: bool(p, "multi", true),
|
|
432
|
+
createTable: bool(p, "create", false),
|
|
433
|
+
align: read(p, "align", "none", codec.oneOf(ALIGNS)),
|
|
434
|
+
pretty: bool(p, "pretty", true),
|
|
435
|
+
xmlRoot: str(p, "root", "rows"),
|
|
436
|
+
xmlRow: str(p, "rowtag", "row"),
|
|
437
|
+
asciiStyle: read(p, "box", "unicode", codec.oneOf(ASCII_STYLES)),
|
|
438
|
+
typeName: str(p, "type", "Row"),
|
|
439
|
+
sheet: str(p, "sheet", "Sheet1"),
|
|
440
|
+
trim: bool(p, "trim", false),
|
|
441
|
+
dropEmpty: bool(p, "empty", false),
|
|
442
|
+
dedupe: bool(p, "dedupe", false),
|
|
443
|
+
transpose: bool(p, "transpose", false),
|
|
444
|
+
caseMode: read(p, "case", "none", codec.oneOf(CASES)),
|
|
445
|
+
find: str(p, "find"),
|
|
446
|
+
replace: str(p, "repl"),
|
|
447
|
+
regex: bool(p, "regex", false),
|
|
448
|
+
ignoreCase: bool(p, "icase", false),
|
|
449
|
+
sortBy: num(p, "sort", -1),
|
|
450
|
+
descending: bool(p, "desc", false),
|
|
451
|
+
columns: p.get("cols") ? read(p, "cols", [], codec.list(codec.number)) : [],
|
|
452
|
+
})
|
|
453
|
+
if (file) return { file, filename: "table.xlsx" }
|
|
454
|
+
return { json: { detected: { format: parsed.format, delimiter: parsed.delimiter }, size: { rows: result.table.rows.length, columns: result.table.columns.length }, removedDuplicates: result.removedDuplicates, removedEmpty: result.removedEmpty, output } }
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
invoice: {
|
|
458
|
+
description: "Gera o PDF do invoice (mesmo JSON do link do site: #invoice=...)",
|
|
459
|
+
params: [doc("invoice", "exemplo", "invoice em JSON (copie do link do site)"), doc("format", "pdf", "pdf | json")],
|
|
460
|
+
run: async (p) => {
|
|
461
|
+
const raw = p.get("invoice")
|
|
462
|
+
const invoice = raw ? invoiceCodec.parse(raw) : EMPTY_INVOICE
|
|
463
|
+
if (!invoice) throw new Error("invoice= precisa ser o JSON do invoice (o mesmo do link do site)")
|
|
464
|
+
if (p.get("format") === "json") return { json: { invoice, totals: totals(invoice.items, invoice.discount, invoice.tax) } }
|
|
465
|
+
return { pdf: await invoicePdf(invoice), filename: `${invoice.number || "invoice"}.pdf` }
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
"pdf-studio": {
|
|
469
|
+
description: "Mescla, divide por intervalo, gira, numera, aplica marca d'água, achata formulários e edita metadados de PDFs",
|
|
470
|
+
params: [
|
|
471
|
+
doc("tab", "organize", TABS.join(" | ")),
|
|
472
|
+
doc("pages", "", "intervalo de páginas (ex.: 1-3,5,4-2; vazio = todas)"),
|
|
473
|
+
doc("rot", DEFAULT_STUDIO_OPTIONS.rotation, ROTATIONS.join(" | ")),
|
|
474
|
+
doc("num", DEFAULT_STUDIO_OPTIONS.pageNumbers, "1 = numera as páginas"),
|
|
475
|
+
doc("numpos", DEFAULT_STUDIO_OPTIONS.numberPosition, NUMBER_POSITIONS.join(" | ")),
|
|
476
|
+
doc("numfmt", DEFAULT_STUDIO_OPTIONS.numberFormat, NUMBER_FORMATS.join(" | ")),
|
|
477
|
+
doc("numstart", DEFAULT_STUDIO_OPTIONS.numberStart, "número inicial"),
|
|
478
|
+
doc("wm", "", "texto da marca d'água"),
|
|
479
|
+
doc("wmsize", DEFAULT_STUDIO_OPTIONS.watermarkSize, "tamanho da fonte da marca d'água (pt)"),
|
|
480
|
+
doc("wmop", DEFAULT_STUDIO_OPTIONS.watermarkOpacity, "opacidade da marca d'água (5-100%)"),
|
|
481
|
+
doc("wmrot", DEFAULT_STUDIO_OPTIONS.watermarkAngle, "ângulo da marca d'água (-90 a 90°)"),
|
|
482
|
+
doc("wmcolor", DEFAULT_STUDIO_OPTIONS.watermarkColor, WATERMARK_COLORS.join(" | ")),
|
|
483
|
+
doc("flatten", DEFAULT_STUDIO_OPTIONS.flattenForms, "1 = achata campos de formulário"),
|
|
484
|
+
doc("clearmeta", DEFAULT_STUDIO_OPTIONS.clearMetadata, "1 = limpa todos os metadados"),
|
|
485
|
+
doc("title", "", "título do PDF"),
|
|
486
|
+
doc("author", "", "autor do PDF"),
|
|
487
|
+
doc("subject", "", "assunto do PDF"),
|
|
488
|
+
doc("keywords", "", "palavras-chave separadas por vírgula"),
|
|
489
|
+
doc("creator", "", "aplicação criadora"),
|
|
490
|
+
doc("in", "exemplo", "PDF(s) em Base64 separados por vírgula (vazio = PDF de exemplo)"),
|
|
491
|
+
doc("format", "pdf", "pdf | json"),
|
|
492
|
+
],
|
|
493
|
+
run: async (p) => {
|
|
494
|
+
const tab = read<PdfStudioTab>(p, "tab", "organize", codec.oneOf(TABS))
|
|
495
|
+
const rawIn = str(p, "in")
|
|
496
|
+
const sources = rawIn
|
|
497
|
+
? rawIn
|
|
498
|
+
.split(",")
|
|
499
|
+
.map((s) => s.trim())
|
|
500
|
+
.filter(Boolean)
|
|
501
|
+
.map((b64) => Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)))
|
|
502
|
+
: [await createSamplePdf()]
|
|
503
|
+
const { bytes, inspection } = await processPdf(sources, {
|
|
504
|
+
pageRange: str(p, "pages"),
|
|
505
|
+
rotation: read<Rotation>(p, "rot", DEFAULT_STUDIO_OPTIONS.rotation, codec.oneOf(ROTATIONS)),
|
|
506
|
+
pageNumbers: bool(p, "num", DEFAULT_STUDIO_OPTIONS.pageNumbers),
|
|
507
|
+
numberPosition: read<NumberPosition>(p, "numpos", DEFAULT_STUDIO_OPTIONS.numberPosition, codec.oneOf(NUMBER_POSITIONS)),
|
|
508
|
+
numberFormat: read<NumberFormat>(p, "numfmt", DEFAULT_STUDIO_OPTIONS.numberFormat, codec.oneOf(NUMBER_FORMATS)),
|
|
509
|
+
numberStart: num(p, "numstart", DEFAULT_STUDIO_OPTIONS.numberStart),
|
|
510
|
+
watermarkText: str(p, "wm"),
|
|
511
|
+
watermarkSize: num(p, "wmsize", DEFAULT_STUDIO_OPTIONS.watermarkSize),
|
|
512
|
+
watermarkOpacity: num(p, "wmop", DEFAULT_STUDIO_OPTIONS.watermarkOpacity),
|
|
513
|
+
watermarkAngle: num(p, "wmrot", DEFAULT_STUDIO_OPTIONS.watermarkAngle),
|
|
514
|
+
watermarkColor: read<WatermarkColor>(p, "wmcolor", DEFAULT_STUDIO_OPTIONS.watermarkColor, codec.oneOf(WATERMARK_COLORS)),
|
|
515
|
+
flattenForms: bool(p, "flatten", DEFAULT_STUDIO_OPTIONS.flattenForms),
|
|
516
|
+
clearMetadata: bool(p, "clearmeta", DEFAULT_STUDIO_OPTIONS.clearMetadata),
|
|
517
|
+
metadata: {
|
|
518
|
+
title: str(p, "title") || undefined,
|
|
519
|
+
author: str(p, "author") || undefined,
|
|
520
|
+
subject: str(p, "subject") || undefined,
|
|
521
|
+
keywords: str(p, "keywords") || undefined,
|
|
522
|
+
creator: str(p, "creator") || undefined,
|
|
523
|
+
},
|
|
524
|
+
})
|
|
525
|
+
const format = read(p, "format", "pdf", codec.oneOf(["pdf", "json"] as const))
|
|
526
|
+
if (format === "json") return { json: { tab, inspection } }
|
|
527
|
+
return { pdf: bytes, filename: "pdf-studio.pdf" }
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
"image-to-pdf": {
|
|
531
|
+
description: "Converte imagens (PNG/JPEG) em um documento PDF com controle de tamanho de folha, orientação, encaixe e margens",
|
|
532
|
+
params: [
|
|
533
|
+
doc("size", DEFAULT_IMAGE_PDF_OPTIONS.pageSize, PAGE_SIZES.join(" | ")),
|
|
534
|
+
doc("orient", DEFAULT_IMAGE_PDF_OPTIONS.orientation, ORIENTATIONS.join(" | ")),
|
|
535
|
+
doc("fit", DEFAULT_IMAGE_PDF_OPTIONS.fit, FIT_MODES.join(" | ")),
|
|
536
|
+
doc("margin", DEFAULT_IMAGE_PDF_OPTIONS.margin, "margem em pt (0-144)"),
|
|
537
|
+
doc("bg", DEFAULT_IMAGE_PDF_OPTIONS.background, BACKGROUNDS.join(" | ")),
|
|
538
|
+
doc("title", "", "título do PDF"),
|
|
539
|
+
doc("author", "", "autor do PDF"),
|
|
540
|
+
doc("in", "exemplo", "imagens PNG/JPEG em Base64 separadas por vírgula (vazio = imagens de exemplo)"),
|
|
541
|
+
doc("format", "pdf", "pdf | json"),
|
|
542
|
+
],
|
|
543
|
+
run: async (p) => {
|
|
544
|
+
const rawIn = str(p, "in")
|
|
545
|
+
const images = rawIn
|
|
546
|
+
? rawIn
|
|
547
|
+
.split(",")
|
|
548
|
+
.map((s) => s.trim())
|
|
549
|
+
.filter(Boolean)
|
|
550
|
+
.map((b64, i) => ({ name: `image-${i + 1}`, bytes: Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)) }))
|
|
551
|
+
: createSampleImages()
|
|
552
|
+
const { bytes, pages } = await imagesToPdf(images, {
|
|
553
|
+
pageSize: read<PageSize>(p, "size", DEFAULT_IMAGE_PDF_OPTIONS.pageSize, codec.oneOf(PAGE_SIZES)),
|
|
554
|
+
orientation: read<Orientation>(p, "orient", DEFAULT_IMAGE_PDF_OPTIONS.orientation, codec.oneOf(ORIENTATIONS)),
|
|
555
|
+
fit: read<FitMode>(p, "fit", DEFAULT_IMAGE_PDF_OPTIONS.fit, codec.oneOf(FIT_MODES)),
|
|
556
|
+
margin: clamp(num(p, "margin", DEFAULT_IMAGE_PDF_OPTIONS.margin), 0, 144),
|
|
557
|
+
background: read<PageBackground>(p, "bg", DEFAULT_IMAGE_PDF_OPTIONS.background, codec.oneOf(BACKGROUNDS)),
|
|
558
|
+
title: str(p, "title"),
|
|
559
|
+
author: str(p, "author"),
|
|
560
|
+
})
|
|
561
|
+
const format = read(p, "format", "pdf", codec.oneOf(["pdf", "json"] as const))
|
|
562
|
+
if (format === "json") return { json: { pageCount: pages.length, byteLength: bytes.byteLength, pages } }
|
|
563
|
+
return { pdf: bytes, filename: "image-to-pdf.pdf" }
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
regex: {
|
|
567
|
+
description: "Testa expressões regulares: ocorrências, grupos numerados e nomeados, substituição (replace) e explicação de tokens",
|
|
568
|
+
params: [
|
|
569
|
+
doc("p", REGEX_PRESETS[0]!.pattern, "expressão regular"),
|
|
570
|
+
doc("f", REGEX_PRESETS[0]!.flags, "flags: g, i, m, s, u"),
|
|
571
|
+
doc("mode", "match", REGEX_MODES.join(" | ")),
|
|
572
|
+
doc("repl", REGEX_PRESETS[0]!.replacement, "texto de substituição ($&, $1, $<nome>)"),
|
|
573
|
+
doc("text", REGEX_PRESETS[0]!.text, "texto de teste"),
|
|
574
|
+
doc("lang", "pt", "pt | en"),
|
|
575
|
+
],
|
|
576
|
+
run: (p) => {
|
|
577
|
+
const preset = REGEX_PRESETS[0]!
|
|
578
|
+
const pattern = p.has("p") ? str(p, "p") : preset.pattern
|
|
579
|
+
const flags = p.has("f") ? str(p, "f") : preset.flags
|
|
580
|
+
const mode = read<RegexMode>(p, "mode", "match", codec.oneOf(REGEX_MODES))
|
|
581
|
+
const replacement = p.has("repl") ? str(p, "repl") : preset.replacement
|
|
582
|
+
const text = p.has("text") ? str(p, "text") : preset.text
|
|
583
|
+
const lang = read(p, "lang", "pt", codec.oneOf(["pt", "en"] as const))
|
|
584
|
+
const evaluation = evaluateRegex(pattern, flags, text, replacement, lang)
|
|
585
|
+
if (!evaluation.ok) throw new Error(evaluation.error ?? "Expressão regular inválida")
|
|
586
|
+
return {
|
|
587
|
+
json: {
|
|
588
|
+
pattern: evaluation.pattern,
|
|
589
|
+
flags: evaluation.flags,
|
|
590
|
+
mode,
|
|
591
|
+
matchCount: evaluation.matches.length,
|
|
592
|
+
matches: evaluation.matches,
|
|
593
|
+
replaced: evaluation.replaced,
|
|
594
|
+
tokens: evaluation.tokens,
|
|
595
|
+
},
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
"dev-ref": {
|
|
600
|
+
description: "Consulta rápida de códigos HTTP (RFC 9110), portas TCP/UDP, tipos MIME e tabela ASCII (dec/hex/oct/bin)",
|
|
601
|
+
params: [
|
|
602
|
+
doc("sec", "all", REF_SECTIONS.join(" | ")),
|
|
603
|
+
doc("q", "", "termo de busca (ex.: 429, 5432, postgres, problem+json, 0x1B)"),
|
|
604
|
+
doc("http", "all", HTTP_CLASSES.join(" | ")),
|
|
605
|
+
],
|
|
606
|
+
run: (p) => {
|
|
607
|
+
const section = read<RefSection>(p, "sec", "all", codec.oneOf(REF_SECTIONS))
|
|
608
|
+
const query = str(p, "q")
|
|
609
|
+
const httpClass = read<HttpClass>(p, "http", "all", codec.oneOf(HTTP_CLASSES))
|
|
610
|
+
const res = queryDevRef({ section, query, httpClass, onlyCommonAscii: section === "all" && !query.trim() })
|
|
611
|
+
return { json: { section, query, httpClass, ...res } }
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
"ai-skills": {
|
|
615
|
+
description: "Catálogo de skills de IA (formato Agent Skills): lista as skills, imprime um arquivo ou instala a pasta da skill",
|
|
616
|
+
params: [
|
|
617
|
+
doc("name", "", `skill a imprimir ou instalar; vazio lista o catálogo; all instala todas (${SKILLS.map((s) => s.name).join(" | ")})`),
|
|
618
|
+
doc("file", "SKILL.md", "arquivo da skill a imprimir (ex.: references/checklist.md)"),
|
|
619
|
+
doc("install", false, "1 grava todos os arquivos em <dir>/<name>/ em vez de imprimir"),
|
|
620
|
+
doc("dir", DEFAULT_SKILLS_DIR, "pasta de destino da instalação (Claude Code no projeto: .claude/skills; para todos os projetos: dir=$HOME/.claude/skills)"),
|
|
621
|
+
],
|
|
622
|
+
run: (p) => {
|
|
623
|
+
const name = str(p, "name")
|
|
624
|
+
const install = bool(p, "install", false)
|
|
625
|
+
const dir = str(p, "dir", DEFAULT_SKILLS_DIR)
|
|
626
|
+
if (!name) {
|
|
627
|
+
return { json: { skills: SKILLS.map((s) => ({ name: s.name, description: s.description, version: s.version, files: s.files.map((f) => f.path) })), next: "name=<skill> imprime o SKILL.md; name=<skill> install=1 instala a pasta" } }
|
|
628
|
+
}
|
|
629
|
+
const selected = name === "all" ? SKILLS : [findSkill(name)].filter((s) => s !== undefined)
|
|
630
|
+
if (selected.length === 0) throw new Error(`Skill desconhecida: ${name}. Disponíveis: ${SKILLS.map((s) => s.name).join(", ")}`)
|
|
631
|
+
if (install) {
|
|
632
|
+
const files = installPlan(selected, dir)
|
|
633
|
+
return { write: files, json: { installed: selected.map((s) => s.name), dir, files: files.map((f) => f.path) } }
|
|
634
|
+
}
|
|
635
|
+
if (name === "all") throw new Error("name=all só funciona com install=1")
|
|
636
|
+
const skill = selected[0] as (typeof SKILLS)[number]
|
|
637
|
+
const path = str(p, "file", "SKILL.md")
|
|
638
|
+
const file = skillFile(skill, path)
|
|
639
|
+
if (!file) throw new Error(`Arquivo desconhecido: ${path}. Arquivos de ${skill.name}: ${skill.files.map((f) => f.path).join(", ")}`)
|
|
640
|
+
return { text: file.content }
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
}
|