+21
| MIT License | ||
| Copyright (c) 2026 Frihet | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+602
-11
| #!/usr/bin/env node | ||
| // src/index.ts | ||
| import { Command as Command6 } from "commander"; | ||
| import { Command as Command10 } from "commander"; | ||
@@ -95,2 +95,8 @@ // src/commands/login.ts | ||
| } | ||
| function outputJson(data) { | ||
| console.log(JSON.stringify(data, null, 2)); | ||
| } | ||
| function shouldOutputJson() { | ||
| return process.argv.includes("--json"); | ||
| } | ||
@@ -158,3 +164,3 @@ // src/commands/login.ts | ||
| } | ||
| var list = new Command2("list").description("List invoices").option("--status <status>", "Filter: draft, sent, paid, overdue, cancelled").option("--limit <n>", "Max results", "20").option("--from <date>", "From date (YYYY-MM-DD)").option("--to <date>", "To date (YYYY-MM-DD)").option("-q, --search <query>", "Search by client name or document number").action(async (opts) => { | ||
| var list = new Command2("list").description("List invoices").option("--status <status>", "Filter: draft, sent, paid, overdue, cancelled").option("--limit <n>", "Max results", "20").option("--from <date>", "From date (YYYY-MM-DD)").option("--to <date>", "To date (YYYY-MM-DD)").option("-q, --search <query>", "Search by client name or document number").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
@@ -170,2 +176,6 @@ const f = client(); | ||
| const page = opts.search ? await f.invoices.search(opts.search, params) : await f.invoices.list(params); | ||
| if (shouldOutputJson()) { | ||
| outputJson(page); | ||
| return; | ||
| } | ||
| if (page.data.length === 0) { | ||
@@ -190,5 +200,9 @@ console.log(dim("No invoices found.")); | ||
| }); | ||
| var get = new Command2("get").description("Get invoice details").argument("<id>", "Invoice ID or document number").action(async (id) => { | ||
| var get = new Command2("get").description("Get invoice details").argument("<id>", "Invoice ID or document number").option("--json", "Output as JSON").action(async (id) => { | ||
| try { | ||
| const inv = await client().invoices.retrieve(id); | ||
| if (shouldOutputJson()) { | ||
| outputJson(inv); | ||
| return; | ||
| } | ||
| console.log(bold(inv.documentNumber ?? inv.id)); | ||
@@ -247,2 +261,27 @@ console.log(`Client: ${inv.clientName}`); | ||
| }); | ||
| var update = new Command2("update").description("Update an invoice").argument("<id>", "Invoice ID").option("--client <name>", "Client name").option("--status <status>", "Status: draft, sent, paid, overdue, cancelled").option("--due <date>", "Due date (YYYY-MM-DD)").option("--notes <text>", "Invoice notes").option("--tax <rate>", "Tax rate").option("--irpf <rate>", "IRPF rate").action(async (id, opts) => { | ||
| try { | ||
| const params = {}; | ||
| if (opts.client !== void 0) params.clientName = opts.client; | ||
| if (opts.status !== void 0) params.status = opts.status; | ||
| if (opts.due !== void 0) params.dueDate = opts.due; | ||
| if (opts.notes !== void 0) params.notes = opts.notes; | ||
| if (opts.tax !== void 0) params.taxRate = parseFloat(opts.tax); | ||
| if (opts.irpf !== void 0) params.irpfRate = parseFloat(opts.irpf); | ||
| const inv = await client().invoices.update(id, params); | ||
| success(`Invoice ${bold(inv.documentNumber ?? inv.id)} updated`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var del = new Command2("delete").description("Delete an invoice").argument("<id>", "Invoice ID").action(async (id) => { | ||
| try { | ||
| await client().invoices.del(id); | ||
| success(`Invoice ${bold(id)} deleted`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var markPaid = new Command2("paid").description("Mark invoice as paid").argument("<id>", "Invoice ID").option("--date <date>", "Payment date (YYYY-MM-DD, defaults to today)").action(async (id, opts) => { | ||
@@ -269,3 +308,15 @@ try { | ||
| }); | ||
| var invoicesCommand = new Command2("invoices").description("Manage invoices").addCommand(list).addCommand(get).addCommand(create).addCommand(markPaid).addCommand(send); | ||
| var pdf = new Command2("pdf").description("Download invoice as PDF").argument("<id>", "Invoice ID").option("-o, --output <path>", "Output file path").action(async (id, opts) => { | ||
| try { | ||
| const { writeFileSync: writeFileSync2 } = await import("fs"); | ||
| const buffer = await client().invoices.pdf(id); | ||
| const outPath = opts.output ?? `invoice-${id}.pdf`; | ||
| writeFileSync2(outPath, Buffer.from(buffer)); | ||
| success(`PDF saved to ${bold(outPath)}`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var invoicesCommand = new Command2("invoices").description("Manage invoices").addCommand(list).addCommand(get).addCommand(create).addCommand(update).addCommand(del).addCommand(markPaid).addCommand(send).addCommand(pdf); | ||
@@ -278,3 +329,3 @@ // src/commands/expenses.ts | ||
| } | ||
| var list2 = new Command3("list").description("List expenses").option("--limit <n>", "Max results", "20").option("--from <date>", "From date (YYYY-MM-DD)").option("--to <date>", "To date (YYYY-MM-DD)").option("-q, --search <query>", "Search expenses").action(async (opts) => { | ||
| var list2 = new Command3("list").description("List expenses").option("--limit <n>", "Max results", "20").option("--from <date>", "From date (YYYY-MM-DD)").option("--to <date>", "To date (YYYY-MM-DD)").option("-q, --search <query>", "Search expenses").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
@@ -284,2 +335,6 @@ const f = client2(); | ||
| const page = opts.search ? await f.expenses.search(opts.search, params) : await f.expenses.list(params); | ||
| if (shouldOutputJson()) { | ||
| outputJson(page); | ||
| return; | ||
| } | ||
| if (page.data.length === 0) { | ||
@@ -306,2 +361,25 @@ console.log(dim("No expenses found.")); | ||
| }); | ||
| var get2 = new Command3("get").description("Get expense details").argument("<id>", "Expense ID").option("--json", "Output as JSON").action(async (id) => { | ||
| try { | ||
| const exp = await client2().expenses.retrieve(id); | ||
| if (shouldOutputJson()) { | ||
| outputJson(exp); | ||
| return; | ||
| } | ||
| console.log(bold(exp.description)); | ||
| console.log(`ID: ${dim(exp.id)}`); | ||
| console.log(`Amount: ${eur(exp.amount)}`); | ||
| console.log(`Category: ${exp.category ?? dim("--")}`); | ||
| console.log(`Date: ${exp.date ?? dim("--")}`); | ||
| console.log(`Vendor: ${exp.vendor ?? dim("--")}`); | ||
| if (exp.invoiceNumber) console.log(`Invoice#: ${exp.invoiceNumber}`); | ||
| if (exp.tax !== void 0) console.log(`Tax: ${eur(exp.tax)}`); | ||
| if (exp.taxType) console.log(`Tax Type: ${exp.taxType}`); | ||
| if (exp.irpf !== void 0) console.log(`IRPF: ${eur(exp.irpf)}`); | ||
| if (exp.taxDeductible !== void 0) console.log(`Deductible: ${exp.taxDeductible ? "yes" : "no"}`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var create2 = new Command3("create").description("Create a new expense").requiredOption("--desc <description>", "Description").requiredOption("--amount <amount>", "Amount in EUR").option("--category <cat>", "Category (office, travel, software, marketing, professional, equipment, insurance, other)").option("--vendor <name>", "Vendor name").option("--date <date>", "Date (YYYY-MM-DD)").option("--tax <rate>", "Tax amount").action(async (opts) => { | ||
@@ -323,3 +401,31 @@ try { | ||
| }); | ||
| var expensesCommand = new Command3("expenses").description("Manage expenses").addCommand(list2).addCommand(create2); | ||
| var update2 = new Command3("update").description("Update an expense").argument("<id>", "Expense ID").option("--desc <description>", "Description").option("--amount <amount>", "Amount in EUR").option("--category <cat>", "Category").option("--vendor <name>", "Vendor name").option("--date <date>", "Date (YYYY-MM-DD)").option("--tax <rate>", "Tax amount").option("--tax-type <type>", "Tax type: IVA, IGIC, IPSI, Exento").option("--irpf <amount>", "IRPF amount").option("--deductible <bool>", "Tax deductible (true/false)").action(async (id, opts) => { | ||
| try { | ||
| const params = {}; | ||
| if (opts.desc !== void 0) params.description = opts.desc; | ||
| if (opts.amount !== void 0) params.amount = parseFloat(opts.amount); | ||
| if (opts.category !== void 0) params.category = opts.category; | ||
| if (opts.vendor !== void 0) params.vendor = opts.vendor; | ||
| if (opts.date !== void 0) params.date = opts.date; | ||
| if (opts.tax !== void 0) params.tax = parseFloat(opts.tax); | ||
| if (opts.taxType !== void 0) params.taxType = opts.taxType; | ||
| if (opts.irpf !== void 0) params.irpf = parseFloat(opts.irpf); | ||
| if (opts.deductible !== void 0) params.taxDeductible = opts.deductible === "true"; | ||
| const exp = await client2().expenses.update(id, params); | ||
| success(`Expense ${bold(exp.id.slice(0, 8))} updated: ${exp.description}`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var del2 = new Command3("delete").description("Delete an expense").argument("<id>", "Expense ID").action(async (id) => { | ||
| try { | ||
| await client2().expenses.del(id); | ||
| success(`Expense ${bold(id)} deleted`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var expensesCommand = new Command3("expenses").description("Manage expenses").addCommand(list2).addCommand(get2).addCommand(create2).addCommand(update2).addCommand(del2); | ||
@@ -332,3 +438,3 @@ // src/commands/clients.ts | ||
| } | ||
| var list3 = new Command4("list").description("List clients").option("--limit <n>", "Max results", "20").option("-q, --search <query>", "Search clients").action(async (opts) => { | ||
| var list3 = new Command4("list").description("List clients").option("--limit <n>", "Max results", "20").option("-q, --search <query>", "Search clients").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
@@ -338,2 +444,6 @@ const f = client3(); | ||
| const page = opts.search ? await f.clients.search(opts.search, params) : await f.clients.list(params); | ||
| if (shouldOutputJson()) { | ||
| outputJson(page); | ||
| return; | ||
| } | ||
| if (page.data.length === 0) { | ||
@@ -377,8 +487,480 @@ console.log(dim("No clients found.")); | ||
| // src/commands/status.ts | ||
| // src/commands/quotes.ts | ||
| import { Command as Command5 } from "commander"; | ||
| import { Frihet as Frihet5 } from "@frihet/sdk"; | ||
| var statusCommand = new Command5("status").description("Quick business health check").option("--month <month>", "Month (YYYY-MM, defaults to current)").action(async (opts) => { | ||
| function client4() { | ||
| return new Frihet5({ apiKey: getApiKey(), baseUrl: getBaseUrl() }); | ||
| } | ||
| function statusColor2(status) { | ||
| switch (status) { | ||
| case "accepted": | ||
| return green(status); | ||
| case "sent": | ||
| return yellow(status); | ||
| case "rejected": | ||
| return red(status); | ||
| case "expired": | ||
| return red(status); | ||
| case "draft": | ||
| return dim(status); | ||
| case "cancelled": | ||
| return dim(status); | ||
| default: | ||
| return status ?? dim("--"); | ||
| } | ||
| } | ||
| var list4 = new Command5("list").description("List quotes").option("--status <status>", "Filter: draft, sent, accepted, rejected, expired, cancelled").option("--limit <n>", "Max results", "20").option("--from <date>", "From date (YYYY-MM-DD)").option("--to <date>", "To date (YYYY-MM-DD)").option("-q, --search <query>", "Search by client name or document number").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
| const f = new Frihet5({ apiKey: getApiKey(), baseUrl: getBaseUrl() }); | ||
| const f = client4(); | ||
| const params = { | ||
| limit: parseInt(opts.limit), | ||
| status: opts.status, | ||
| from: opts.from, | ||
| to: opts.to, | ||
| q: opts.search | ||
| }; | ||
| const page = opts.search ? await f.quotes.search(opts.search, params) : await f.quotes.list(params); | ||
| if (shouldOutputJson()) { | ||
| outputJson(page); | ||
| return; | ||
| } | ||
| if (page.data.length === 0) { | ||
| console.log(dim("No quotes found.")); | ||
| return; | ||
| } | ||
| const rows = page.data.map((q) => [ | ||
| q.documentNumber ?? q.id.slice(0, 8), | ||
| q.clientName, | ||
| eur(q.total), | ||
| statusColor2(q.status), | ||
| q.validUntil ?? dim("--") | ||
| ]); | ||
| table(rows, ["Number", "Client", "Amount", "Status", "Valid Until"]); | ||
| console.log(dim(` | ||
| ${page.total} total, showing ${page.data.length}`)); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var get3 = new Command5("get").description("Get quote details").argument("<id>", "Quote ID or document number").option("--json", "Output as JSON").action(async (id) => { | ||
| try { | ||
| const q = await client4().quotes.retrieve(id); | ||
| if (shouldOutputJson()) { | ||
| outputJson(q); | ||
| return; | ||
| } | ||
| console.log(bold(q.documentNumber ?? q.id)); | ||
| console.log(`Client: ${q.clientName}`); | ||
| console.log(`Status: ${statusColor2(q.status)}`); | ||
| console.log(`Amount: ${eur(q.total)}`); | ||
| console.log(`Valid Until: ${q.validUntil ?? dim("--")}`); | ||
| if (q.notes) console.log(`Notes: ${q.notes}`); | ||
| if (q.items?.length) { | ||
| console.log(dim("\nItems:")); | ||
| table( | ||
| q.items.map((item) => [ | ||
| item.description, | ||
| String(item.quantity), | ||
| eur(item.unitPrice), | ||
| eur(item.quantity * item.unitPrice) | ||
| ]), | ||
| ["Description", "Qty", "Price", "Subtotal"] | ||
| ); | ||
| } | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var create4 = new Command5("create").description("Create a new quote").requiredOption("--client <name>", "Client name").requiredOption("--item <items...>", 'Items as "description,qty,price" (repeatable)').option("--valid-until <date>", "Valid until date (YYYY-MM-DD)").option("--tax <rate>", "Tax rate (e.g. 21)").option("--notes <text>", "Quote notes").option("--send <email>", "Send immediately to this email").action(async (opts) => { | ||
| try { | ||
| const items = opts.item.map((raw) => { | ||
| const parts = raw.split(","); | ||
| if (parts.length < 3) throw new Error(`Invalid item format: "${raw}". Use "description,qty,price"`); | ||
| return { | ||
| description: parts[0].trim(), | ||
| quantity: parseFloat(parts[1]), | ||
| unitPrice: parseFloat(parts[2]) | ||
| }; | ||
| }); | ||
| const f = client4(); | ||
| const q = await f.quotes.create({ | ||
| clientName: opts.client, | ||
| items, | ||
| validUntil: opts.validUntil, | ||
| taxRate: opts.tax ? parseFloat(opts.tax) : void 0, | ||
| notes: opts.notes | ||
| }); | ||
| success(`Quote ${bold(q.documentNumber ?? q.id)} created (${eur(q.total)})`); | ||
| if (opts.send) { | ||
| await f.quotes.send(q.id, { recipientEmail: opts.send }); | ||
| success(`Sent to ${opts.send}`); | ||
| } | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var update3 = new Command5("update").description("Update a quote").argument("<id>", "Quote ID").option("--client <name>", "Client name").option("--valid-until <date>", "Valid until date (YYYY-MM-DD)").option("--tax <rate>", "Tax rate").option("--notes <text>", "Notes").option("--status <status>", "Status: draft, sent, accepted, rejected, expired, cancelled").action(async (id, opts) => { | ||
| try { | ||
| const params = {}; | ||
| if (opts.client !== void 0) params.clientName = opts.client; | ||
| if (opts.validUntil !== void 0) params.validUntil = opts.validUntil; | ||
| if (opts.tax !== void 0) params.taxRate = parseFloat(opts.tax); | ||
| if (opts.notes !== void 0) params.notes = opts.notes; | ||
| if (opts.status !== void 0) params.status = opts.status; | ||
| const q = await client4().quotes.update(id, params); | ||
| success(`Quote ${bold(q.documentNumber ?? q.id)} updated`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var del3 = new Command5("delete").description("Delete a quote").argument("<id>", "Quote ID").action(async (id) => { | ||
| try { | ||
| await client4().quotes.del(id); | ||
| success(`Quote ${bold(id)} deleted`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var pdf2 = new Command5("pdf").description("Download quote as PDF").argument("<id>", "Quote ID").option("-o, --output <path>", "Output file path").action(async (id, opts) => { | ||
| try { | ||
| const { writeFileSync: writeFileSync2 } = await import("fs"); | ||
| const buffer = await client4().quotes.pdf(id); | ||
| const outPath = opts.output ?? `quote-${id}.pdf`; | ||
| writeFileSync2(outPath, Buffer.from(buffer)); | ||
| success(`PDF saved to ${bold(outPath)}`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var send2 = new Command5("send").description("Send quote by email").argument("<id>", "Quote ID").requiredOption("--to <email>", "Recipient email").option("--message <text>", "Custom message").action(async (id, opts) => { | ||
| try { | ||
| const result = await client4().quotes.send(id, { | ||
| recipientEmail: opts.to, | ||
| customMessage: opts.message | ||
| }); | ||
| success(`Quote sent (message ID: ${dim(result.messageId)})`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var quotesCommand = new Command5("quotes").description("Manage quotes").addCommand(list4).addCommand(get3).addCommand(create4).addCommand(update3).addCommand(del3).addCommand(pdf2).addCommand(send2); | ||
| // src/commands/products.ts | ||
| import { Command as Command6 } from "commander"; | ||
| import { Frihet as Frihet6 } from "@frihet/sdk"; | ||
| function client5() { | ||
| return new Frihet6({ apiKey: getApiKey(), baseUrl: getBaseUrl() }); | ||
| } | ||
| var list5 = new Command6("list").description("List products").option("--limit <n>", "Max results", "20").option("-q, --search <query>", "Search products").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
| const f = client5(); | ||
| const params = { limit: parseInt(opts.limit), q: opts.search }; | ||
| const page = opts.search ? await f.products.search(opts.search, params) : await f.products.list(params); | ||
| if (shouldOutputJson()) { | ||
| outputJson(page); | ||
| return; | ||
| } | ||
| if (page.data.length === 0) { | ||
| console.log(dim("No products found.")); | ||
| return; | ||
| } | ||
| table( | ||
| page.data.map((p) => [ | ||
| p.id.slice(0, 8), | ||
| p.name, | ||
| eur(p.unitPrice), | ||
| p.sku ?? dim("--"), | ||
| p.category ?? dim("--"), | ||
| p.isActive === false ? red("inactive") : green("active") | ||
| ]), | ||
| ["ID", "Name", "Price", "SKU", "Category", "Status"] | ||
| ); | ||
| console.log(dim(` | ||
| ${page.total} total, showing ${page.data.length}`)); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var get4 = new Command6("get").description("Get product details").argument("<id>", "Product ID").option("--json", "Output as JSON").action(async (id) => { | ||
| try { | ||
| const p = await client5().products.retrieve(id); | ||
| if (shouldOutputJson()) { | ||
| outputJson(p); | ||
| return; | ||
| } | ||
| console.log(bold(p.name)); | ||
| console.log(`ID: ${dim(p.id)}`); | ||
| console.log(`Price: ${eur(p.unitPrice)}`); | ||
| console.log(`SKU: ${p.sku ?? dim("--")}`); | ||
| console.log(`Category: ${p.category ?? dim("--")}`); | ||
| console.log(`Status: ${p.isActive === false ? red("inactive") : green("active")}`); | ||
| if (p.description) console.log(`Desc: ${p.description}`); | ||
| if (p.taxRate !== void 0) console.log(`Tax: ${p.taxRate}%`); | ||
| if (p.irpfRate !== void 0) console.log(`IRPF: ${p.irpfRate}%`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var create5 = new Command6("create").description("Create a new product").requiredOption("--name <name>", "Product name").requiredOption("--price <price>", "Unit price in EUR").option("--sku <sku>", "SKU code").option("--category <cat>", "Category").option("--desc <description>", "Description").option("--tax <rate>", "Tax rate (e.g. 21)").action(async (opts) => { | ||
| try { | ||
| const p = await client5().products.create({ | ||
| name: opts.name, | ||
| unitPrice: parseFloat(opts.price), | ||
| sku: opts.sku, | ||
| category: opts.category, | ||
| description: opts.desc, | ||
| taxRate: opts.tax ? parseFloat(opts.tax) : void 0 | ||
| }); | ||
| success(`Product ${bold(p.name)} created (${eur(p.unitPrice)}) ${dim(p.id.slice(0, 8))}`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var update4 = new Command6("update").description("Update a product").argument("<id>", "Product ID").option("--name <name>", "Product name").option("--price <price>", "Unit price in EUR").option("--sku <sku>", "SKU code").option("--category <cat>", "Category").option("--desc <description>", "Description").option("--tax <rate>", "Tax rate").option("--active <bool>", "Active status (true/false)").action(async (id, opts) => { | ||
| try { | ||
| const params = {}; | ||
| if (opts.name !== void 0) params.name = opts.name; | ||
| if (opts.price !== void 0) params.unitPrice = parseFloat(opts.price); | ||
| if (opts.sku !== void 0) params.sku = opts.sku; | ||
| if (opts.category !== void 0) params.category = opts.category; | ||
| if (opts.desc !== void 0) params.description = opts.desc; | ||
| if (opts.tax !== void 0) params.taxRate = parseFloat(opts.tax); | ||
| if (opts.active !== void 0) params.isActive = opts.active === "true"; | ||
| const p = await client5().products.update(id, params); | ||
| success(`Product ${bold(p.name)} updated`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var del4 = new Command6("delete").description("Delete a product").argument("<id>", "Product ID").action(async (id) => { | ||
| try { | ||
| await client5().products.del(id); | ||
| success(`Product ${bold(id)} deleted`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var productsCommand = new Command6("products").description("Manage products").addCommand(list5).addCommand(get4).addCommand(create5).addCommand(update4).addCommand(del4); | ||
| // src/commands/vendors.ts | ||
| import { Command as Command7 } from "commander"; | ||
| import { Frihet as Frihet7 } from "@frihet/sdk"; | ||
| function client6() { | ||
| return new Frihet7({ apiKey: getApiKey(), baseUrl: getBaseUrl() }); | ||
| } | ||
| var list6 = new Command7("list").description("List vendors").option("--limit <n>", "Max results", "20").option("-q, --search <query>", "Search vendors").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
| const f = client6(); | ||
| const params = { limit: parseInt(opts.limit), q: opts.search }; | ||
| const page = opts.search ? await f.vendors.search(opts.search, params) : await f.vendors.list(params); | ||
| if (shouldOutputJson()) { | ||
| outputJson(page); | ||
| return; | ||
| } | ||
| if (page.data.length === 0) { | ||
| console.log(dim("No vendors found.")); | ||
| return; | ||
| } | ||
| table( | ||
| page.data.map((v) => [ | ||
| v.id.slice(0, 8), | ||
| v.name, | ||
| v.email ?? dim("--"), | ||
| v.phone ?? dim("--"), | ||
| v.taxId ?? dim("--") | ||
| ]), | ||
| ["ID", "Name", "Email", "Phone", "Tax ID"] | ||
| ); | ||
| console.log(dim(` | ||
| ${page.total} total, showing ${page.data.length}`)); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var get5 = new Command7("get").description("Get vendor details").argument("<id>", "Vendor ID").option("--json", "Output as JSON").action(async (id) => { | ||
| try { | ||
| const v = await client6().vendors.retrieve(id); | ||
| if (shouldOutputJson()) { | ||
| outputJson(v); | ||
| return; | ||
| } | ||
| console.log(bold(v.name)); | ||
| console.log(`ID: ${dim(v.id)}`); | ||
| console.log(`Email: ${v.email ?? dim("--")}`); | ||
| console.log(`Phone: ${v.phone ?? dim("--")}`); | ||
| console.log(`Tax ID: ${v.taxId ?? dim("--")}`); | ||
| if (v.address) { | ||
| const addr = typeof v.address === "string" ? v.address : [v.address.street, v.address.city, v.address.postalCode, v.address.country].filter(Boolean).join(", "); | ||
| console.log(`Address: ${addr}`); | ||
| } | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var create6 = new Command7("create").description("Create a new vendor").requiredOption("--name <name>", "Vendor name").option("--email <email>", "Email").option("--phone <phone>", "Phone").option("--tax-id <taxId>", "Tax ID (NIF/VAT)").action(async (opts) => { | ||
| try { | ||
| const v = await client6().vendors.create({ | ||
| name: opts.name, | ||
| email: opts.email, | ||
| phone: opts.phone, | ||
| taxId: opts.taxId | ||
| }); | ||
| success(`Vendor ${bold(v.name)} created (${dim(v.id.slice(0, 8))})`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var update5 = new Command7("update").description("Update a vendor").argument("<id>", "Vendor ID").option("--name <name>", "Vendor name").option("--email <email>", "Email").option("--phone <phone>", "Phone").option("--tax-id <taxId>", "Tax ID (NIF/VAT)").action(async (id, opts) => { | ||
| try { | ||
| const params = {}; | ||
| if (opts.name !== void 0) params.name = opts.name; | ||
| if (opts.email !== void 0) params.email = opts.email; | ||
| if (opts.phone !== void 0) params.phone = opts.phone; | ||
| if (opts.taxId !== void 0) params.taxId = opts.taxId; | ||
| const v = await client6().vendors.update(id, params); | ||
| success(`Vendor ${bold(v.name)} updated`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var del5 = new Command7("delete").description("Delete a vendor").argument("<id>", "Vendor ID").action(async (id) => { | ||
| try { | ||
| await client6().vendors.del(id); | ||
| success(`Vendor ${bold(id)} deleted`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var vendorsCommand = new Command7("vendors").description("Manage vendors").addCommand(list6).addCommand(get5).addCommand(create6).addCommand(update5).addCommand(del5); | ||
| // src/commands/webhooks.ts | ||
| import { Command as Command8 } from "commander"; | ||
| import { Frihet as Frihet8 } from "@frihet/sdk"; | ||
| function client7() { | ||
| return new Frihet8({ apiKey: getApiKey(), baseUrl: getBaseUrl() }); | ||
| } | ||
| function statusColor3(status) { | ||
| switch (status) { | ||
| case "active": | ||
| return green(status); | ||
| case "inactive": | ||
| return red(status); | ||
| case "paused": | ||
| return yellow(status); | ||
| default: | ||
| return status ?? dim("--"); | ||
| } | ||
| } | ||
| var list7 = new Command8("list").description("List webhooks").option("--limit <n>", "Max results", "20").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
| const page = await client7().webhooks.list({ limit: parseInt(opts.limit) }); | ||
| if (shouldOutputJson()) { | ||
| outputJson(page); | ||
| return; | ||
| } | ||
| if (page.data.length === 0) { | ||
| console.log(dim("No webhooks found.")); | ||
| return; | ||
| } | ||
| table( | ||
| page.data.map((w) => [ | ||
| w.id.slice(0, 8), | ||
| w.name ?? dim("--"), | ||
| w.url.length > 50 ? w.url.slice(0, 47) + "..." : w.url, | ||
| statusColor3(w.status), | ||
| String(w.events.length) + " events" | ||
| ]), | ||
| ["ID", "Name", "URL", "Status", "Events"] | ||
| ); | ||
| console.log(dim(` | ||
| ${page.total} total, showing ${page.data.length}`)); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var get6 = new Command8("get").description("Get webhook details").argument("<id>", "Webhook ID").option("--json", "Output as JSON").action(async (id) => { | ||
| try { | ||
| const w = await client7().webhooks.retrieve(id); | ||
| if (shouldOutputJson()) { | ||
| outputJson(w); | ||
| return; | ||
| } | ||
| console.log(bold(w.name ?? w.id)); | ||
| console.log(`ID: ${dim(w.id)}`); | ||
| console.log(`URL: ${w.url}`); | ||
| console.log(`Status: ${statusColor3(w.status)}`); | ||
| console.log(`Events: ${w.events.join(", ")}`); | ||
| if (w.secret) console.log(`Secret: ${dim(w.secret.slice(0, 8) + "...")}`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var create7 = new Command8("create").description("Create a new webhook").requiredOption("--url <url>", "Webhook endpoint URL").requiredOption("--events <events...>", "Events to listen for (e.g. invoice.created invoice.paid)").option("--name <name>", "Webhook name").option("--secret <secret>", "Signing secret").action(async (opts) => { | ||
| try { | ||
| const w = await client7().webhooks.create({ | ||
| url: opts.url, | ||
| events: opts.events, | ||
| name: opts.name, | ||
| secret: opts.secret | ||
| }); | ||
| success(`Webhook ${bold(w.name ?? w.id.slice(0, 8))} created`); | ||
| console.log(`URL: ${w.url}`); | ||
| console.log(`Events: ${w.events.join(", ")}`); | ||
| if (w.secret) console.log(`Secret: ${dim(w.secret)}`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var update6 = new Command8("update").description("Update a webhook").argument("<id>", "Webhook ID").option("--url <url>", "Webhook endpoint URL").option("--events <events...>", "Events to listen for").option("--name <name>", "Webhook name").option("--status <status>", "Status: active, inactive, paused").option("--secret <secret>", "Signing secret").action(async (id, opts) => { | ||
| try { | ||
| const params = {}; | ||
| if (opts.url !== void 0) params.url = opts.url; | ||
| if (opts.events !== void 0) params.events = opts.events; | ||
| if (opts.name !== void 0) params.name = opts.name; | ||
| if (opts.status !== void 0) params.status = opts.status; | ||
| if (opts.secret !== void 0) params.secret = opts.secret; | ||
| const w = await client7().webhooks.update(id, params); | ||
| success(`Webhook ${bold(w.name ?? w.id.slice(0, 8))} updated`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var del6 = new Command8("delete").description("Delete a webhook").argument("<id>", "Webhook ID").action(async (id) => { | ||
| try { | ||
| await client7().webhooks.del(id); | ||
| success(`Webhook ${bold(id)} deleted`); | ||
| } catch (err) { | ||
| error(err instanceof Error ? err.message : String(err)); | ||
| process.exit(1); | ||
| } | ||
| }); | ||
| var webhooksCommand = new Command8("webhooks").description("Manage webhooks").addCommand(list7).addCommand(get6).addCommand(create7).addCommand(update6).addCommand(del6); | ||
| // src/commands/status.ts | ||
| import { Command as Command9 } from "commander"; | ||
| import { Frihet as Frihet9 } from "@frihet/sdk"; | ||
| var statusCommand = new Command9("status").description("Quick business health check").option("--month <month>", "Month (YYYY-MM, defaults to current)").option("--json", "Output as JSON").action(async (opts) => { | ||
| try { | ||
| const f = new Frihet9({ apiKey: getApiKey(), baseUrl: getBaseUrl() }); | ||
| const [ctx, summary] = await Promise.all([ | ||
@@ -388,2 +970,6 @@ f.intelligence.context(), | ||
| ]); | ||
| if (shouldOutputJson()) { | ||
| outputJson({ context: ctx, summary }); | ||
| return; | ||
| } | ||
| const business = ctx.business; | ||
@@ -429,3 +1015,4 @@ const plan = ctx.plan; | ||
| // src/index.ts | ||
| var program = new Command6().name("frihet").version("1.0.0").description("Frihet CLI \u2014 manage your business from the terminal"); | ||
| var CLI_VERSION = true ? "1.0.1" : "0.0.0-dev"; | ||
| var program = new Command10().name("frihet").version(CLI_VERSION).description("Frihet CLI \u2014 manage your business from the terminal"); | ||
| program.addCommand(loginCommand); | ||
@@ -435,3 +1022,7 @@ program.addCommand(invoicesCommand); | ||
| program.addCommand(clientsCommand); | ||
| program.addCommand(quotesCommand); | ||
| program.addCommand(productsCommand); | ||
| program.addCommand(vendorsCommand); | ||
| program.addCommand(webhooksCommand); | ||
| program.addCommand(statusCommand); | ||
| program.parse(); |
+5
-3
| { | ||
| "name": "frihet", | ||
| "version": "1.0.0", | ||
| "version": "1.0.1", | ||
| "description": "Frihet CLI — manage your business from the terminal", | ||
@@ -10,3 +10,4 @@ "type": "module", | ||
| "files": [ | ||
| "dist" | ||
| "dist", | ||
| "LICENSE" | ||
| ], | ||
@@ -17,3 +18,4 @@ "scripts": { | ||
| "typecheck": "tsc --noEmit", | ||
| "dev": "tsx src/index.ts" | ||
| "dev": "tsx src/index.ts", | ||
| "prepublishOnly": "pnpm run build" | ||
| }, | ||
@@ -20,0 +22,0 @@ "keywords": [ |
45533
142.38%4
33.33%1013
144.1%