var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res, err) => function __init() {
  if (err) throw err[0];
  try {
    return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
  } catch (e) {
    throw err = [e], e;
  }
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};

// src/tools/shared/validation.ts
import { z } from "zod";
function guardInput(input, maxBytes = 15e5) {
  const seen = /* @__PURE__ */ new Set();
  function walk(value, depth) {
    if (depth > 12) throw new InputError("This file has too many nested fields.");
    if (typeof value === "string" && value.length > 1e5) throw new InputError("A text field is too long.");
    if (typeof value === "number" && !Number.isFinite(value)) throw new InputError("Numbers must be finite.");
    if (!value || typeof value !== "object") return;
    if (seen.has(value)) throw new InputError("Circular data is not supported.");
    seen.add(value);
    if (Array.isArray(value) && value.length > 1e3) throw new InputError("This file contains too many items.");
    if (!Array.isArray(value) && ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new InputError("Only plain data objects are supported.");
    for (const [key, child] of Object.entries(value)) {
      if (["__proto__", "prototype", "constructor"].includes(key)) throw new InputError("This file contains an unsafe field.");
      walk(child, depth + 1);
    }
    seen.delete(value);
  }
  walk(input, 0);
  if (new TextEncoder().encode(JSON.stringify(input)).byteLength > maxBytes) throw new InputError("This file is too large.");
}
var InputError;
var init_validation = __esm({
  "src/tools/shared/validation.ts"() {
    "use strict";
    z.config({ jitless: true });
    InputError = class extends Error {
      issues;
      constructor(message, issues = []) {
        super(message);
        this.name = "InputError";
        this.issues = issues;
      }
    };
  }
});

// src/tools/shared/decimal.ts
import Decimal from "decimal.js";
function roundedProduct(a, b) {
  return new MoneyDecimal(a).times(b).toFixed(2);
}
function sumMoney(values) {
  return values.reduce((sum, value) => sum.plus(value), new MoneyDecimal(0)).toFixed(2);
}
function percentageMoney(base, percent) {
  return new MoneyDecimal(base).times(percent).dividedBy(100).toFixed(2);
}
var MoneyDecimal;
var init_decimal = __esm({
  "src/tools/shared/decimal.ts"() {
    "use strict";
    MoneyDecimal = Decimal.clone({ precision: 36, rounding: Decimal.ROUND_HALF_UP });
  }
});

// src/tools/shared/dates.ts
function stamp(value) {
  const [year, month, day] = value.split("-").map(Number);
  return Date.UTC(year, month - 1, day);
}
function isDate(value) {
  if (typeof value !== "string" || !/^(19|20|21)\d{2}-\d{2}-\d{2}$/.test(value)) return false;
  const time = stamp(value);
  return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value;
}
function addDays(value, count) {
  if (!isDate(value) || !Number.isSafeInteger(count)) throw new Error("Invalid date");
  return new Date(stamp(value) + count * DAY).toISOString().slice(0, 10);
}
function calendarDays(start, end) {
  if (!isDate(start) || !isDate(end) || end < start) throw new Error("Invalid date range");
  return Math.round((stamp(end) - stamp(start)) / DAY) + 1;
}
function workingDays(start, end) {
  const count = calendarDays(start, end);
  let total = Math.floor(count / 7) * 5;
  const weekday = new Date(stamp(start)).getUTCDay();
  for (let index = 0; index < count % 7; index++) if (![0, 6].includes((weekday + index) % 7)) total++;
  return total;
}
function nextWorkingDay(value) {
  let next = addDays(value, 1);
  while ([0, 6].includes(new Date(stamp(next)).getUTCDay())) next = addDays(next, 1);
  return next;
}
var DAY;
var init_dates = __esm({
  "src/tools/shared/dates.ts"() {
    "use strict";
    DAY = 864e5;
  }
});

// src/tools/shared/pdf-font.ts
var pdf_font_exports = {};
__export(pdf_font_exports, {
  assertPdfTextSupported: () => assertPdfTextSupported
});
async function assertPdfTextSupported(texts, bytes) {
  let font = coverage.get(bytes);
  if (!font) {
    const fontkit = await import("fontkit");
    font = fontkit.create(bytes);
    coverage.set(bytes, font);
  }
  const unsupported = /* @__PURE__ */ new Set();
  for (const text2 of texts) for (const character of text2) {
    if (/^[\n\r\t]$/.test(character)) continue;
    const point = character.codePointAt(0);
    if (!font.hasGlyphForCodePoint(point)) unsupported.add(point);
    if (unsupported.size >= 6) break;
  }
  if (unsupported.size) {
    const points = [...unsupported].map((point) => `U+${point.toString(16).toUpperCase().padStart(4, "0")}`).join(", ");
    throw new InputError(`The PDF font cannot display some characters (${points}). Replace those characters for PDF export, or save JSON to keep the original text. Your draft has not changed.`);
  }
}
var coverage;
var init_pdf_font = __esm({
  "src/tools/shared/pdf-font.ts"() {
    "use strict";
    init_validation();
    coverage = /* @__PURE__ */ new WeakMap();
  }
});

// src/tools/planner/core.ts
var core_exports = {};
__export(core_exports, {
  PLANNER_LIMITS: () => PLANNER_LIMITS,
  calculatePlan: () => calculatePlan,
  exportPlanCsv: () => exportPlanCsv,
  newTaskId: () => newTaskId,
  planSchema: () => planSchema,
  plannerExamples: () => plannerExamples,
  schema: () => schema,
  taskSchema: () => taskSchema,
  validatePlan: () => validatePlan
});
function validatePlan(input) {
  const warnings = [];
  const unsafe = inspectInput(input);
  if (unsafe) return { ok: false, errors: [{ path: "", message: unsafe }], warnings };
  const parsed = planSchema.safeParse(input);
  if (!parsed.success) return {
    ok: false,
    errors: parsed.error.issues.map((issue) => ({ path: issue.path.join("."), message: issue.message })),
    warnings
  };
  const plan = parsed.data;
  const errors = [];
  const byId = /* @__PURE__ */ new Map();
  plan.tasks.forEach((task, index) => {
    if (byId.has(task.id)) errors.push({ path: `tasks.${index}.id`, message: "Each task needs a unique ID." });
    byId.set(task.id, task);
    if (task.end < task.start) errors.push({ path: `tasks.${index}.end`, message: "The end date must be on or after the start date." });
    if (new Set(task.predecessors).size !== task.predecessors.length) errors.push({ path: `tasks.${index}.predecessors`, message: "A predecessor can only be listed once." });
  });
  plan.tasks.forEach((task, index) => {
    task.predecessors.forEach((predecessor) => {
      const preceding = byId.get(predecessor);
      if (predecessor === task.id) errors.push({ path: `tasks.${index}.predecessors`, message: "A task cannot depend on itself." });
      else if (!preceding) errors.push({ path: `tasks.${index}.predecessors`, message: "A predecessor task is missing. Choose an existing task." });
      else if (task.start < nextWorkingDay(preceding.end)) warnings.push({
        path: `tasks.${index}.start`,
        message: `\u201C${task.title}\u201D starts before the next working day after \u201C${preceding.title}\u201D finishes. Dates have not been moved.`
      });
    });
  });
  const visited = /* @__PURE__ */ new Set();
  const visiting = /* @__PURE__ */ new Set();
  const visit = (taskId) => {
    if (visiting.has(taskId)) return false;
    if (visited.has(taskId)) return true;
    visiting.add(taskId);
    for (const predecessor of byId.get(taskId)?.predecessors ?? []) if (!visit(predecessor)) return false;
    visiting.delete(taskId);
    visited.add(taskId);
    return true;
  };
  if (plan.tasks.some((task) => !visit(task.id))) errors.push({ path: "tasks", message: "These dependencies form a loop. Remove a predecessor to break the cycle." });
  return errors.length ? { ok: false, errors, warnings } : { ok: true, value: plan, warnings };
}
function inspectInput(input) {
  const seen = /* @__PURE__ */ new Set();
  let nodes = 0;
  const inspect = (value, depth) => {
    if (++nodes > 6e3 || depth > 8) return "This plan is too complex to import.";
    if (typeof value === "string" && value.length > 8e3) return "A field in this plan is too long.";
    if (!value || typeof value !== "object") return null;
    if (seen.has(value)) return "This plan contains a circular object.";
    seen.add(value);
    if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return "This plan contains an unsupported object.";
    for (const [key, child] of Object.entries(value)) {
      if (["__proto__", "prototype", "constructor"].includes(key)) return "This plan contains an unsafe property.";
      const problem = inspect(child, depth + 1);
      if (problem) return problem;
    }
    seen.delete(value);
    return null;
  };
  return inspect(input, 0);
}
function calculatePlan(plan) {
  const checked = validatePlan(plan);
  if (!checked.ok) throw new Error("Correct the plan before calculating its budget.");
  const taskCosts = /* @__PURE__ */ Object.create(null);
  let hours = new MoneyDecimal(0);
  for (const task of plan.tasks) {
    taskCosts[task.id] = roundedProduct(task.hours, task.rate);
    hours = hours.plus(task.hours);
  }
  const base = sumMoney(Object.values(taskCosts));
  const contingency = percentageMoney(base, plan.contingencyPercent);
  const start = plan.tasks.map((task) => task.start).sort()[0];
  const end = plan.tasks.map((task) => task.end).sort().at(-1);
  return {
    totalHours: hours.toString(),
    baseCost: base,
    contingency,
    totalCost: sumMoney([base, contingency]),
    taskCosts,
    start,
    end,
    calendarDays: calendarDays(start, end),
    workingDays: workingDays(start, end)
  };
}
function csvCell(value, numeric = false) {
  const safe = !numeric && /^[\s\u0000-\u001f]*[=+@-]|^[\t\r\n]/.test(value) ? `'${value}` : value;
  return `"${safe.replaceAll('"', '""')}"`;
}
function exportPlanCsv(plan) {
  const summary = calculatePlan(plan);
  const rows = [[
    "Task ID",
    "Phase",
    "Task",
    "Start (inclusive)",
    "End (inclusive)",
    "Hours",
    "Hourly rate",
    "Task cost",
    "Currency",
    "Progress (%)",
    "Predecessor IDs"
  ].map((value) => csvCell(value)).join(",")];
  for (const task of plan.tasks) rows.push([
    csvCell(task.id),
    csvCell(task.phase),
    csvCell(task.title),
    csvCell(task.start),
    csvCell(task.end),
    csvCell(task.hours, true),
    csvCell(task.rate, true),
    csvCell(summary.taskCosts[task.id], true),
    csvCell(plan.currency),
    csvCell(task.progressPercent, true),
    csvCell(task.predecessors.join("; "))
  ].join(","));
  return `\uFEFF${rows.join("\r\n")}\r
`;
}
function newTaskId() {
  return `task-${globalThis.crypto.randomUUID()}`;
}
function example(title, description, hours, rate) {
  const phases = ["Discovery", "Design", "Development", "Integration", "Testing", "Launch"];
  const titles = ["Agree on goals and scope", "Design the main screens and experience", "Build the main features", "Connect the required systems", "Test with the team and fix issues", "Prepare the launch and handover"];
  let start = "2026-09-07";
  const tasks = phases.map((phase, index) => {
    const end = addDays(start, index === 2 ? 18 : index === 5 ? 2 : 4);
    const task = {
      id: `task-${index + 1}`,
      phase,
      title: titles[index],
      start,
      end,
      hours: String(hours[index]),
      rate,
      progressPercent: "0",
      predecessors: index ? [`task-${index}`] : []
    };
    start = nextWorkingDay(end);
    return task;
  });
  return { schemaVersion: 1, kind: "project-plan", title, description, currency: "CAD", referenceDate: "2026-09-07", contingencyPercent: "15", tasks };
}
var PLANNER_LIMITS, decimal2, date2, id, taskSchema, planSchema, schema, plannerExamples;
var init_core = __esm({
  "src/tools/planner/core.ts"() {
    "use strict";
    init_validation();
    init_decimal();
    init_dates();
    PLANNER_LIMITS = { tasks: 100, fileBytes: 256e3 };
    decimal2 = (maximum) => z.string().regex(/^\d{1,9}(?:\.\d{1,4})?$/, "Use a positive number with up to four decimal places.").refine((value) => Number(value) <= maximum, `Use ${maximum.toLocaleString("en-CA")} or less.`);
    date2 = z.string().refine(isDate, "Use a real date in YYYY-MM-DD format.");
    id = z.string().regex(/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/, "Use a valid task ID.");
    taskSchema = z.strictObject({
      id,
      phase: z.string().trim().min(1).max(80),
      title: z.string().trim().min(1).max(160),
      start: date2,
      end: date2,
      hours: decimal2(1e5),
      rate: decimal2(1e6),
      progressPercent: decimal2(100),
      predecessors: z.array(id).max(PLANNER_LIMITS.tasks)
    });
    planSchema = z.strictObject({
      schemaVersion: z.literal(1),
      kind: z.literal("project-plan"),
      title: z.string().trim().min(1).max(160),
      description: z.string().max(4e3),
      currency: z.enum(["CAD", "USD", "EUR", "GBP", "AUD"]),
      referenceDate: date2,
      contingencyPercent: decimal2(100),
      tasks: z.array(taskSchema).min(1, "Add at least one task.").max(PLANNER_LIMITS.tasks)
    });
    schema = planSchema;
    plannerExamples = {
      "customer-portal": example("Customer portal", "An illustrative plan for a secure customer portal with account access, requests and updates. Replace the sample hours, rates and dates with your own.", [24, 40, 160, 40, 48, 16], "100"),
      "internal-application": example("Internal business application", "An illustrative plan for an internal application that replaces spreadsheet handoffs. Sample rates are not Essential Designs prices.", [32, 48, 200, 64, 64, 24], "100"),
      "legacy-modernization": example("Legacy software modernization", "An illustrative plan for improving an existing system, connecting data and moving users safely. Investigate migration requirements before setting your own estimate.", [48, 40, 240, 96, 80, 32], "100")
    };
  }
});

// cli/tools-cli.ts
import { readFile as readFile2, stat, realpath, open } from "node:fs/promises";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";

// src/tools/document/model.ts
init_validation();
init_decimal();
init_dates();

// src/tools/document/logo.ts
var MAX_BYTES = 2e6;
var crcTable = Uint32Array.from({ length: 256 }, (_, index) => {
  let value = index;
  for (let bit = 0; bit < 8; bit++) value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
  return value >>> 0;
});
function crc32(bytes, start, end) {
  let value = 4294967295;
  for (let i = start; i < end; i++) value = crcTable[(value ^ bytes[i]) & 255] ^ value >>> 8;
  return (value ^ 4294967295) >>> 0;
}
function inspectLogo(bytes) {
  if (bytes.byteLength > MAX_BYTES || bytes.byteLength < 24) throw new Error("Choose a PNG or JPEG logo smaller than 2 MB.");
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
  let width = 0;
  let height = 0;
  let mime;
  if ([137, 80, 78, 71, 13, 10, 26, 10].every((value, i) => bytes[i] === value)) {
    if (view.getUint32(8) !== 13 || String.fromCharCode(...bytes.slice(12, 16)) !== "IHDR") throw new Error("The PNG logo is not valid.");
    mime = "image/png";
    width = view.getUint32(16);
    height = view.getUint32(20);
    if (width < 1 || height < 1 || width > 4096 || height > 4096 || width * height > 8e6) throw new Error("Choose a logo up to 4096 pixels per side and 8 million pixels in total.");
    let cursor = 8;
    let imageData = false;
    let ended = false;
    while (cursor + 12 <= bytes.length) {
      const length = view.getUint32(cursor);
      const type = String.fromCharCode(...bytes.slice(cursor + 4, cursor + 8));
      if (length > bytes.length - cursor - 12) throw new Error("The PNG logo is incomplete.");
      if (crc32(bytes, cursor + 4, cursor + 8 + length) !== view.getUint32(cursor + 8 + length)) throw new Error("The PNG logo is damaged. Choose another image file.");
      if (type === "acTL") throw new Error("Use a still PNG or JPEG logo, rather than an animated image.");
      if (type === "IDAT") imageData = true;
      cursor += length + 12;
      if (type === "IEND") {
        ended = length === 0 && cursor === bytes.length;
        break;
      }
    }
    if (!imageData || !ended) throw new Error("The PNG logo is incomplete.");
  } else if (bytes[0] === 255 && bytes[1] === 216) {
    if (bytes.at(-2) !== 255 || bytes.at(-1) !== 217) throw new Error("The JPEG logo is incomplete.");
    mime = "image/jpeg";
    let cursor = 2;
    while (cursor + 4 <= bytes.length) {
      if (bytes[cursor] !== 255) throw new Error("The JPEG logo is not valid.");
      while (bytes[cursor] === 255) cursor++;
      const marker = bytes[cursor++];
      if (marker === 217 || marker === 218) break;
      if (marker === 1 || marker >= 208 && marker <= 215) continue;
      if (cursor + 2 > bytes.length) break;
      const length = view.getUint16(cursor);
      if (length < 2 || cursor + length > bytes.length) throw new Error("The JPEG logo is incomplete.");
      if ([192, 193, 194].includes(marker) && length >= 8) {
        height = view.getUint16(cursor + 3);
        width = view.getUint16(cursor + 5);
        break;
      }
      cursor += length;
    }
  } else throw new Error("Only local PNG and JPEG logos are supported. SVG and remote images are not accepted.");
  if (width < 1 || height < 1 || width > 4096 || height > 4096 || width * height > 8e6) throw new Error("Choose a logo up to 4096 pixels per side and 8 million pixels in total.");
  return { mime, width, height };
}
function inspectLogoDataUrl(url) {
  const match = /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/]+={0,2})$/.exec(url);
  if (!match || match[2].length > 2666672 || match[2].length % 4 !== 0) throw new Error("Use a local PNG or JPEG logo.");
  const binary = atob(match[2]);
  const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
  const info = inspectLogo(bytes);
  if (info.mime !== match[1]) throw new Error("The logo file type does not match its contents.");
  return { ...info, bytes };
}

// src/tools/document/model.ts
var DOCUMENT_LIMITS = { items: 200, text: 8e3, logoBytes: 2e6, jsonBytes: 3e6 };
var documentTitles = { quotation: "Quotation", "work-order": "Work order", "service-report": "Service report" };
var text = (max = DOCUMENT_LIMITS.text) => z.string().max(max).refine((v) => !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/u.test(v), "Remove hidden control characters from this text.");
var required = (max = 240) => text(max).refine((v) => v.trim().length > 0, "Please enter a value.");
var decimal = (max, places = 6) => z.string().regex(new RegExp(`^(?:0|[1-9]\\d{0,10})(?:\\.\\d{1,${places}})?$`), "Enter a positive number or zero, using a decimal point and no commas.").refine((v) => {
  try {
    return new MoneyDecimal(v).lte(max);
  } catch {
    return false;
  }
}, `Enter a number no greater than ${max.toLocaleString("en-CA")}.`);
var date = z.string().refine(isDate, "Enter a real date in YYYY-MM-DD format.");
var optionalDate = z.union([z.literal(""), date]);
var item = { id: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/), description: required(1200), quantity: decimal(1e6) };
var common = {
  schemaVersion: z.literal(1),
  kind: z.literal("document"),
  templateId: z.enum(["quotation", "work-order", "service-report"]),
  paper: z.enum(["A4", "Letter"]),
  accent: z.enum(["#6B46FF", "#173B35", "#26364B", "#171717"]),
  business: z.strictObject({ name: required(240), contact: text(1200) }),
  client: z.strictObject({ name: required(240), contact: text(1200) }),
  number: required(80),
  date,
  validUntil: optionalDate,
  location: text(800),
  person: text(240),
  description: text(),
  notes: text(),
  terms: text(),
  findings: text(),
  recommendations: text(),
  nextSteps: text(),
  logo: z.string().max(27e5).refine((v) => {
    try {
      inspectLogoDataUrl(v);
      return true;
    } catch {
      return false;
    }
  }, "Use a valid local PNG or JPEG logo, no larger than 2 MB or 4096 pixels per side.").optional()
};
var documentSchema = z.discriminatedUnion("pricing", [
  z.strictObject({ ...common, pricing: z.literal(true), currency: z.enum(["CAD", "USD"]), discount: decimal(1e14, 2), taxes: z.array(z.strictObject({ name: required(40), rate: decimal(100) })).max(2), items: z.array(z.strictObject({ ...item, unitPrice: decimal(1e8) })).max(DOCUMENT_LIMITS.items) }),
  z.strictObject({ ...common, pricing: z.literal(false), items: z.array(z.strictObject(item)).max(DOCUMENT_LIMITS.items) })
]).superRefine((data, ctx) => {
  if (data.templateId === "quotation" && !data.pricing) ctx.addIssue({ code: "custom", path: ["pricing"], message: "Quotations include pricing. Choose a work order or service report to hide prices." });
  if (data.validUntil && data.validUntil < data.date) ctx.addIssue({ code: "custom", path: ["validUntil"], message: "The valid-until date must be on or after the issue date." });
  if (new Set(data.items.map((i) => i.id)).size !== data.items.length) ctx.addIssue({ code: "custom", path: ["items"], message: "Each line item needs a different ID." });
  if (data.pricing && data.items.every((i) => /^\d+(\.\d+)?$/.test(i.quantity) && /^\d+(\.\d+)?$/.test(i.unitPrice)) && /^\d+(\.\d+)?$/.test(data.discount)) {
    const subtotal = data.items.reduce((sum, line) => sum.plus(new MoneyDecimal(line.quantity).times(line.unitPrice).toDecimalPlaces(2, MoneyDecimal.ROUND_HALF_UP)), new MoneyDecimal(0));
    if (new MoneyDecimal(data.discount).gt(subtotal)) ctx.addIssue({ code: "custom", path: ["discount"], message: "The discount cannot be greater than the subtotal." });
  }
});
function inspectKeys(value, depth = 0) {
  if (depth > 8) throw new Error("The document contains too many nested objects.");
  if (!value || typeof value !== "object") return;
  if (!Array.isArray(value) && ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new Error("Only plain data objects are supported.");
  if (Array.isArray(value) && value.length > DOCUMENT_LIMITS.items) throw new Error("Documents support up to 200 line items.");
  for (const key of Object.keys(value)) {
    if (["__proto__", "prototype", "constructor"].includes(key)) throw new Error("The document contains an unsupported object key.");
    inspectKeys(value[key], depth + 1);
  }
}
function validateDocument(input) {
  try {
    inspectKeys(input);
    if (new TextEncoder().encode(JSON.stringify(input)).byteLength > DOCUMENT_LIMITS.jsonBytes) throw new Error("This document is too large. Keep the saved JSON below 3 MB.");
  } catch (error) {
    return { ok: false, errors: [{ path: "", message: error instanceof Error ? error.message : "Invalid document." }], warnings: [] };
  }
  const parsed = documentSchema.safeParse(input);
  if (!parsed.success) return { ok: false, errors: parsed.error.issues.map((i) => ({ path: i.path.join("."), message: i.message })), warnings: [] };
  const warnings = parsed.data.items.length === 0 ? [{ path: "items", message: "This document has no line items. Add items if you need them." }] : [];
  return { ok: true, value: parsed.data, warnings };
}
function calculateDocument(data) {
  if (!data.pricing) return { pricing: false, items: data.items.map((i) => ({ id: i.id, description: i.description, quantity: i.quantity })) };
  const lines = data.items.map((i) => ({ id: i.id, total: roundedProduct(i.quantity, i.unitPrice) }));
  const subtotal = new MoneyDecimal(sumMoney(lines.map((line) => line.total)));
  const taxable = subtotal.minus(data.discount);
  const taxes = data.taxes.map((tax) => ({ name: tax.name, rate: tax.rate, amount: percentageMoney(taxable.toFixed(2), tax.rate) }));
  const total = taxes.reduce((sum, tax) => sum.plus(tax.amount), taxable);
  return { pricing: true, currency: data.currency, lines, subtotal: subtotal.toFixed(2), discount: new MoneyDecimal(data.discount).toFixed(2), taxable: taxable.toFixed(2), taxes, total: total.toFixed(2) };
}
var sampleCommon = {
  schemaVersion: 1,
  kind: "document",
  paper: "A4",
  accent: "#173B35",
  business: { name: "Northline Services", contact: "123 Example Avenue, Vancouver, BC\nhello@example.com \xB7 604-555-0100" },
  client: { name: "Cedar & Co.", contact: "456 Sample Street, Vancouver, BC\nTaylor Martin \xB7 client@example.com" },
  number: "Q-2026-001",
  date: "2026-09-06",
  validUntil: "2026-10-06",
  location: "456 Sample Street, Vancouver, BC",
  person: "Alex Morgan",
  description: "Office maintenance and equipment setup.",
  notes: "Thank you for the opportunity to support your team.",
  terms: "Valid for 30 days. Please confirm the scope and schedule before work begins.",
  findings: "",
  recommendations: "",
  nextSteps: ""
};
var documentExamples = {
  quotation: { ...sampleCommon, templateId: "quotation", pricing: true, currency: "CAD", discount: "0.00", taxes: [{ name: "Tax 1", rate: "0" }, { name: "Tax 2", rate: "0" }], items: [{ id: "item-1", description: "On-site assessment and project planning", quantity: "2.5", unitPrice: "199.95" }, { id: "item-2", description: "Equipment setup and testing", quantity: "3", unitPrice: "100.00" }] },
  "work-order": { ...sampleCommon, templateId: "work-order", number: "WO-2026-001", validUntil: "", pricing: false, notes: "Check in at reception on arrival. Confirm access with the site contact.", terms: "", items: [{ id: "item-1", description: "Inspect workstations and document any issues", quantity: "8" }, { id: "item-2", description: "Install approved replacement parts", quantity: "3" }] },
  "service-report": { ...sampleCommon, templateId: "service-report", number: "SR-2026-001", validUntil: "", pricing: false, description: "Completed workstation checks and installed the approved replacement parts.", notes: "", terms: "", findings: "All eight workstations passed the final checks. Two cables showed signs of wear.", recommendations: "Replace worn cables during the next planned maintenance visit.", nextSteps: "Client to confirm a suitable date for the follow-up visit.", items: [{ id: "item-1", description: "Workstations inspected and tested", quantity: "8" }, { id: "item-2", description: "Replacement parts installed", quantity: "3" }] }
};

// src/tools/document/pdf.ts
init_pdf_font();
var FONT = "NotoSans";
var money = (amount, currency) => `${currency} ${new Intl.NumberFormat("en-CA", { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(Number(amount))}`;
var unitPrice = (amount, currency) => `${currency} ${new Intl.NumberFormat("en-CA", { minimumFractionDigits: 2, maximumFractionDigits: 6 }).format(Number(amount))}`;
function createDocumentTemplate(data, _assets) {
  const valid = validateDocument(data);
  if (!valid.ok) throw new Error(`Please correct the document before exporting: ${valid.errors[0]?.message}`);
  const width = data.paper === "A4" ? 210 : 215.9;
  const height = data.paper === "A4" ? 297 : 279.4;
  const margin = 18;
  const contentWidth = width - margin * 2;
  const schemas = [];
  const input = {};
  let y = margin;
  const addText = (name, value, options = {}) => {
    if (!value.trim()) return;
    const blockHeight = options.height ?? 12;
    schemas.push({ name, type: "text", position: { x: margin, y }, width: options.width ?? contentWidth, height: blockHeight, fontName: FONT, fontSize: options.size ?? 10, fontColor: options.color ?? "#202020", lineHeight: 1.25, characterSpacing: 0, alignment: "left", verticalAlignment: "top", overflow: "expand", required: false, readOnly: false });
    input[name] = value;
    y += blockHeight + (options.gap ?? 3);
  };
  addText("document_title", documentTitles[data.templateId].toUpperCase(), { size: 24, color: data.accent, height: 13, width: data.logo ? contentWidth - 45 : contentWidth, gap: 5 });
  if (data.logo) {
    const logo = inspectLogoDataUrl(data.logo);
    const scale = Math.min(35 / logo.width, 18 / logo.height);
    schemas.push({ name: "company_logo", type: "image", position: { x: width - margin - logo.width * scale, y: margin }, width: logo.width * scale, height: logo.height * scale, required: false, readOnly: false });
    input.company_logo = data.logo;
  }
  addText("business_name", data.business.name, { size: 15, height: 9 });
  addText("business_contact", data.business.contact, { size: 9, height: 14, color: "#505050", gap: 5 });
  addText("client_details", `PREPARED FOR
${data.client.name}${data.client.contact ? `
${data.client.contact}` : ""}`, { height: 22 });
  const dateLabel = data.templateId === "quotation" ? "Issued" : data.templateId === "work-order" ? "Scheduled" : "Service date";
  const details = [`${documentTitles[data.templateId]} number: ${data.number}`, `${dateLabel}: ${data.date}`];
  if (data.templateId === "quotation" && data.validUntil) details.push(`Valid until: ${data.validUntil}`);
  if (data.templateId !== "quotation" && data.person) details.push(`Assigned to: ${data.person}`);
  if (data.templateId !== "quotation" && data.location) details.push(`Location: ${data.location}`);
  addText("document_details", details.join("\n"), { size: 9, height: 20, gap: 4 });
  const workLabel = data.templateId === "service-report" ? "WORK COMPLETED" : data.templateId === "work-order" ? "WORK DESCRIPTION" : "SCOPE";
  if (data.description) addText("work_description", `${workLabel}
${data.description}`, { height: 15, gap: 5 });
  const totals = calculateDocument(data);
  if (data.items.length) {
    const priced = data.pricing && totals.pricing;
    const head = priced ? ["Description", "Qty", "Unit price", "Amount"] : ["Description", "Qty"];
    const rows = data.items.map((item2, i) => priced && "unitPrice" in item2 ? [item2.description, item2.quantity, unitPrice(item2.unitPrice, data.currency), money(totals.lines[i].total, data.currency)] : [item2.description, item2.quantity]);
    const borders = { top: 0, right: 0, bottom: 0.15, left: 0 };
    const padding = { top: 3, right: 0, bottom: 3, left: 0 };
    schemas.push({ name: "line_items", type: "table", position: { x: margin, y }, width: contentWidth, height: 28, content: "[]", showHead: true, repeatHead: true, head, headWidthPercentages: priced ? [52, 10, 19, 19] : [85, 15], tableStyles: { borderWidth: 0, borderColor: "#d9dfdf" }, headStyles: { fontName: FONT, fontSize: 9, characterSpacing: 0, alignment: "left", verticalAlignment: "middle", lineHeight: 1.2, fontColor: "#ffffff", backgroundColor: data.accent, borderColor: data.accent, borderWidth: borders, padding }, bodyStyles: { fontName: FONT, fontSize: 9, characterSpacing: 0, alignment: "left", verticalAlignment: "top", lineHeight: 1.25, fontColor: "#202020", backgroundColor: "#ffffff", alternateBackgroundColor: "#f4f6f5", borderColor: "#d9dfdf", borderWidth: borders, padding }, columnStyles: {}, required: false, readOnly: false });
    input.line_items = JSON.stringify(rows);
    y += 33;
  }
  if (totals.pricing) {
    addText("totals", [`Subtotal: ${money(totals.subtotal, totals.currency)}`, `Discount: ${money(totals.discount, totals.currency)}`, `Amount before tax: ${money(totals.taxable, totals.currency)}`, ...totals.taxes.map((t) => `${t.name} (${t.rate}%): ${money(t.amount, totals.currency)}`), `TOTAL: ${money(totals.total, totals.currency)}`].join("\n"), { size: 10, height: 29, gap: 6 });
  }
  if (data.templateId === "service-report") {
    for (const [key, label2] of [["findings", "FINDINGS"], ["recommendations", "RECOMMENDATIONS"], ["nextSteps", "NEXT STEPS"]]) if (data[key]) addText(key, `${label2}
${data[key]}`, { height: 12, gap: 5 });
  }
  if (data.notes) addText("notes", `${data.templateId === "work-order" ? "INSTRUCTIONS" : "NOTES"}
${data.notes}`, { height: 12, gap: 5 });
  if (data.templateId === "quotation" && data.terms) addText("terms", `TERMS
${data.terms}`, { size: 9, color: "#505050", height: 12 });
  return { template: { basePdf: { width, height, padding: [margin, margin, margin, margin] }, schemas: [schemas], pdfmeVersion: "6.1.12" }, inputs: [input] };
}
async function generateDocumentPdf(data, assets) {
  const { template, inputs } = createDocumentTemplate(data, assets);
  await assertPdfTextSupported(Object.entries(inputs[0]).filter(([name]) => name !== "company_logo").map(([, value]) => value), assets.fontBytes);
  const [{ generate }, { text: text2, table, image }] = await Promise.all([import("@pdfme/generator"), import("@pdfme/schemas")]);
  return generate({ template, inputs, plugins: { text: text2, table, image }, options: { font: { [FONT]: { data: assets.fontBytes.slice().buffer, fallback: true, subset: true } }, lang: "en" } });
}

// cli/tools-cli.ts
init_core();

// src/tools/shared/pdf.ts
async function createTextPdf(blocks, assets, pageSize = "A4") {
  const { assertPdfTextSupported: assertPdfTextSupported2 } = await Promise.resolve().then(() => (init_pdf_font(), pdf_font_exports));
  await assertPdfTextSupported2(blocks.map((block) => block.text), assets.fontBytes);
  const [{ generate }, { text: text2 }] = await Promise.all([import("@pdfme/generator"), import("@pdfme/schemas")]);
  const width = pageSize === "Letter" ? 215.9 : 210;
  const height = pageSize === "Letter" ? 279.4 : 297;
  const schemas = blocks.map((block, index) => ({ name: `block${index}`, type: "text", position: { x: 16, y: 16 + index * 14 }, width: width - 32, height: 10, fontName: "NotoSans", fontSize: block.size ?? 10, lineHeight: 1.35, fontColor: block.color ?? "#171717", alignment: "left", verticalAlignment: "top", overflow: "expand", required: false }));
  const inputs = Object.fromEntries(blocks.map((block, index) => [`block${index}`, block.text]));
  return generate({ template: { basePdf: { width, height, padding: [16, 16, 16, 16] }, schemas: [schemas] }, inputs: [inputs], plugins: { text: text2 }, options: { font: { NotoSans: { data: new Uint8Array(assets.fontBytes), fallback: true } } } });
}

// src/tools/planner/pdf.ts
init_dates();
init_core();
async function generatePlanPdf(plan, assets, pageSize = "A4") {
  const result = calculatePlan(plan);
  const money2 = (value) => `${plan.currency} ${value}`;
  const blocks = [
    { text: "Project Timeline & Budget Planner", size: 18, bold: true },
    { text: plan.title, size: 16, bold: true },
    { text: plan.description || "Project description not supplied." },
    { text: "Planning estimate based on your inputs\u2014not a quotation." },
    { text: `Reference date: ${plan.referenceDate}. Total hours: ${result.totalHours}.` },
    { text: `Base cost: ${money2(result.baseCost)}. Contingency (${plan.contingencyPercent}%): ${money2(result.contingency)}. Total estimated cost: ${money2(result.totalCost)}.`, size: 12, bold: true },
    { text: `Schedule: ${result.start} to ${result.end}, inclusive. ${result.calendarDays} calendar days; ${result.workingDays} Monday\u2013Friday working days. Holidays are not deducted. Hours are entered separately from duration.` },
    { text: "Dependencies mean finish-to-start with zero lag. A following task should start no earlier than the next working day after its predecessor ends. Dependent tasks are never moved automatically." },
    { text: "The numbered timeline appears on separate landscape pages after the task details. Each page repeats the same date scale and shows at most 18 tasks; numbers refer to the task list below. Exact inclusive dates are printed beside every task." },
    { text: "Task details", size: 15, bold: true }
  ];
  for (const [index, task] of plan.tasks.entries()) blocks.push(
    { text: `${index + 1}. ${task.title} \u2014 ${task.phase}`, size: 12, bold: true },
    { text: `${task.start} to ${task.end} (inclusive). ${task.hours} hours \xD7 ${money2(task.rate)} per hour = ${money2(result.taskCosts[task.id])}. Progress: ${task.progressPercent}%.` },
    { text: `Task ID: ${task.id}. Predecessors: ${task.predecessors.map((id3) => `${plan.tasks.findIndex((other) => other.id === id3) + 1} (${id3})`).join(", ") || "None"}.` }
  );
  const { validatePlan: validatePlan2 } = await Promise.resolve().then(() => (init_core(), core_exports));
  const checked = validatePlan2(plan);
  if (checked.warnings.length) blocks.push({ text: "Schedule conflicts to review", size: 14, bold: true }, ...checked.warnings.map((warning) => ({ text: warning.message })));
  const textPdf = await createTextPdf(blocks, assets, pageSize);
  const { PDFDocument, StandardFonts, rgb } = await import("@pdfme/pdf-lib");
  const doc = await PDFDocument.load(textPdf);
  const font = await doc.embedFont(StandardFonts.Helvetica);
  const bold = await doc.embedFont(StandardFonts.HelveticaBold);
  const totalPages = Math.ceil(plan.tasks.length / 18);
  for (let group = 0; group < totalPages; group++) {
    const page = doc.addPage(pageSize === "Letter" ? [792, 612] : [841.89, 595.28]);
    const width = page.getWidth();
    const height = page.getHeight();
    const labelX = 30;
    const chartX = 245;
    const chartWidth = width - chartX - 30;
    page.drawText(`Project timeline ${group + 1} of ${totalPages}`, { x: labelX, y: height - 35, size: 18, font: bold });
    page.drawText("Task numbers match the full task details. Dates include both the first and last day.", { x: labelX, y: height - 55, size: 10, font });
    const ticks = 5;
    for (let tick = 0; tick <= ticks; tick++) {
      const offset = Math.round((result.calendarDays - 1) * tick / ticks);
      const label2 = addDays(result.start, offset);
      const x = chartX + chartWidth * tick / ticks;
      page.drawText(label2, { x: Math.min(x - 20, width - 85), y: height - 83, size: 8, font });
      page.drawLine({ start: { x, y: height - 92 }, end: { x, y: 40 }, thickness: 0.4, color: rgb(0.87, 0.87, 0.89) });
    }
    for (const [row, task] of plan.tasks.slice(group * 18, group * 18 + 18).entries()) {
      const index = group * 18 + row;
      const y = height - 116 - row * 24;
      const offset = calendarDays(result.start, task.start) - 1;
      const duration = calendarDays(task.start, task.end);
      const x = chartX + offset / result.calendarDays * chartWidth;
      const barWidth = Math.max(1.5, duration / result.calendarDays * chartWidth);
      page.drawText(`${index + 1}. ${task.start} - ${task.end}`, { x: labelX, y, size: 9, font });
      page.drawRectangle({ x, y: y - 2, width: Math.min(barWidth, width - 30 - x), height: 13, color: rgb(0.42, 0.27, 1) });
    }
    page.drawText("Long spans use the same proportional scale on every timeline page. Exact dates appear at left.", { x: labelX, y: 20, size: 8, font });
  }
  doc.setTitle(plan.title);
  doc.setSubject("Planning estimate based on user inputs; not a quotation.");
  doc.setCreator("Essential Tools Project Planner");
  return doc.save();
}

// src/tools/mapper/core.ts
init_validation();
var PROCESS_LIMITS = Object.freeze({ nodes: 100, edges: 200, sourceBytes: 3e4, jsonBytes: 15e4, svgBytes: 5e6, label: 160, description: 400 });
var id2 = z.string().regex(/^[A-Za-z][A-Za-z0-9_]{0,47}$/, "Use a letter followed by letters, numbers or underscores.");
var label = z.string().trim().min(1, "Enter a name for this step.").max(PROCESS_LIMITS.label);
var nodeSchema = z.strictObject({ id: id2, type: z.enum(["start", "end", "process", "decision", "system", "store"]), label, team: z.string().max(100), description: z.string().max(PROCESS_LIMITS.description) });
var edgeSchema = z.strictObject({ id: id2, from: id2, to: id2, label: z.string().max(80) });
var graphSchema = z.strictObject({ title: label, direction: z.enum(["TB", "LR"]), nodes: z.array(nodeSchema).min(1).max(PROCESS_LIMITS.nodes), edges: z.array(edgeSchema).max(PROCESS_LIMITS.edges) });
var processSchema = z.discriminatedUnion("mode", [
  z.strictObject({ schemaVersion: z.literal(1), tool: z.literal("process-mapper"), mode: z.literal("guided"), graph: graphSchema }),
  z.strictObject({ schemaVersion: z.literal(1), tool: z.literal("process-mapper"), mode: z.literal("source"), source: z.string().max(PROCESS_LIMITS.sourceBytes), preservedGraph: graphSchema.nullable() })
]);
function encodeLabel(value) {
  return value.replace(/[\x00-\x1f\x7f"\[\]{}()<>`|;#&\\]/g, (char) => `#${char.codePointAt(0)};`);
}
function compileGuided(graph) {
  const lines = [`flowchart ${graph.direction}`];
  for (const node2 of graph.nodes) {
    const text2 = encodeLabel(node2.label);
    const shape2 = node2.type === "start" || node2.type === "end" ? `(["${text2}"])` : node2.type === "decision" ? `{"${text2}"}` : node2.type === "store" ? `[("${text2}")]` : node2.type === "system" ? `[["${text2}"]]` : `["${text2}"]`;
    lines.push(`  ${node2.id}${shape2}`);
  }
  for (const edge2 of graph.edges) lines.push(`  ${edge2.from} -->${edge2.label ? `|"${encodeLabel(edge2.label)}"|` : ""} ${edge2.to}`);
  return lines.join("\n");
}
function inspectGraph(graph, prefix) {
  const errors = [], warnings = [];
  const nodes = /* @__PURE__ */ new Set(), edges = /* @__PURE__ */ new Set(), connected = /* @__PURE__ */ new Set();
  graph.nodes.forEach((node2, index) => {
    if (nodes.has(node2.id)) errors.push({ path: `${prefix}.nodes.${index}.id`, message: "Each step needs a different ID." });
    nodes.add(node2.id);
  });
  graph.edges.forEach((edge2, index) => {
    if (edges.has(edge2.id)) errors.push({ path: `${prefix}.edges.${index}.id`, message: "Each connection needs a different ID." });
    edges.add(edge2.id);
    if (!nodes.has(edge2.from) || !nodes.has(edge2.to)) errors.push({ path: `${prefix}.edges.${index}`, message: "Choose existing steps at both ends of this connection." });
    connected.add(edge2.from);
    connected.add(edge2.to);
  });
  if (graph.nodes.length > 1) graph.nodes.forEach((node2, index) => {
    if (!connected.has(node2.id)) warnings.push({ path: `${prefix}.nodes.${index}`, message: `${node2.label} has no connections. Check whether it belongs in this process.` });
  });
  if (graph.nodes.length > 1) {
    const reached = /* @__PURE__ */ new Set([graph.nodes[0].id]);
    let changed = true;
    while (changed) {
      changed = false;
      for (const edge2 of graph.edges) {
        if (reached.has(edge2.from) && !reached.has(edge2.to)) {
          reached.add(edge2.to);
          changed = true;
        }
        if (reached.has(edge2.to) && !reached.has(edge2.from)) {
          reached.add(edge2.from);
          changed = true;
        }
      }
    }
    if (graph.nodes.some((node2) => !reached.has(node2.id))) warnings.push({ path: `${prefix}.edges`, message: "This map has separate groups of steps. Check whether they should be connected." });
  }
  return { errors, warnings };
}
var tokenId = "[A-Za-z][A-Za-z0-9_]{0,47}";
var quoted = '"[^"\\r\\n]*"';
var shape = `(?:\\[\\(${quoted}\\)\\]|\\[\\[${quoted}\\]\\]|\\(\\[${quoted}\\]\\)|\\[${quoted}\\]|\\{${quoted}\\})`;
var nodeToken = `${tokenId}(?:${shape})?`;
var nodeLine = new RegExp(`^(${tokenId})${shape}$`, "u");
var edgeLine = new RegExp(`^(${nodeToken})\\s*(-->|-\\.->|==>)(?:\\|(${quoted})\\|)?\\s*(${nodeToken})$`, "u");
function validateSource(source) {
  if (new TextEncoder().encode(source).length > PROCESS_LIMITS.sourceBytes) return [{ path: "source", message: "Keep Mermaid source below 30 KB." }];
  if (!source.trim()) return [{ path: "source", message: "Enter a flowchart or sequence diagram." }];
  if (/%%\s*\{|^\s*---|<\/?[A-Za-z!]|\b(?:javascript|vbscript|data|https?|file):|@\{|:::|\$\$/imu.test(source)) return [{ path: "source", message: "Configuration, HTML, links, images, icons and custom styling are not supported." }];
  const lines = source.replace(/\r\n?/g, "\n").split("\n").map((value, index) => ({ value: value.trim(), index })).filter((line) => line.value && !line.value.startsWith("%%"));
  const first = lines.shift()?.value;
  const flow = /^(?:flowchart|graph)\s+(?:TB|TD|LR|RL|BT)$/.test(first ?? "");
  const sequence = first === "sequenceDiagram";
  if (!flow && !sequence) return [{ path: "source", message: "Start with \u201Cflowchart TB\u201D, \u201Cflowchart LR\u201D or \u201CsequenceDiagram\u201D. Other diagram types are not supported." }];
  const errors = [], ids = /* @__PURE__ */ new Set();
  let edges = 0, depth = 0;
  if (lines.length > 420) return [{ path: "source", message: "Use no more than 420 source statements." }];
  for (const { value, index } of lines) {
    let accepted = false;
    if (flow) {
      const node2 = value.match(nodeLine), edge2 = value.match(edgeLine);
      if (node2) {
        ids.add(node2[1]);
        accepted = true;
      }
      if (edge2) {
        ids.add(edge2[1].match(new RegExp(`^${tokenId}`))[0]);
        ids.add(edge2[4].match(new RegExp(`^${tokenId}`))[0]);
        edges++;
        accepted = true;
      }
    } else {
      const actor = value.match(new RegExp(`^(?:participant|actor)\\s+(${tokenId})(?:\\s+as\\s+([^<>\\r\\n{}]+))?$`, "u"));
      const message = value.match(new RegExp(`^(${tokenId})\\s*(?:-->>|->>|-->|->|--x|-x|--\\)|-\\))\\s*(${tokenId})\\s*:\\s*([^<>\\r\\n{}]+)$`, "u"));
      const note = value.match(new RegExp(`^Note\\s+(?:(?:right|left)\\s+of\\s+(${tokenId})|over\\s+(${tokenId})(?:\\s*,\\s*(${tokenId}))?)\\s*:\\s*([^<>\\r\\n{}]+)$`, "u"));
      const active = value.match(new RegExp(`^(?:activate|deactivate)\\s+(${tokenId})$`));
      if (actor) {
        ids.add(actor[1]);
        accepted = true;
      }
      if (message) {
        ids.add(message[1]);
        ids.add(message[2]);
        edges++;
        accepted = true;
      }
      if (note) {
        [note[1], note[2], note[3]].filter(Boolean).forEach((id3) => ids.add(id3));
        accepted = true;
      }
      if (active) {
        ids.add(active[1]);
        accepted = true;
      }
      if (/^(?:alt|opt|loop|par)\s+[^<>{}\r\n]+$/u.test(value)) {
        depth++;
        accepted = depth <= 8;
      }
      if (/^(?:else|and)(?:\s+[^<>{}\r\n]+)?$/u.test(value) && depth > 0) accepted = true;
      if (value === "end" && depth > 0) {
        depth--;
        accepted = true;
      }
      if (value === "autonumber") accepted = true;
    }
    if (!accepted) errors.push({ path: `source.line.${index + 1}`, message: `Line ${index + 1} is outside the supported syntax. Use quoted flowchart labels or simple sequence messages; see the source guide.` });
    if (errors.length >= 8) break;
  }
  if (depth) errors.push({ path: "source", message: "Close each sequence block with \u201Cend\u201D." });
  if (!ids.size) errors.push({ path: "source", message: "Add at least one step or participant." });
  if (ids.size > PROCESS_LIMITS.nodes || edges > PROCESS_LIMITS.edges) errors.push({ path: "source", message: "Use at most 100 steps/participants and 200 connections/messages." });
  return errors;
}
function validateProcess(input) {
  try {
    guardInput(input, PROCESS_LIMITS.jsonBytes);
  } catch {
    return { ok: false, errors: [{ path: "", message: "This file is too large or contains unsupported objects, unsafe keys or too many nested levels." }], warnings: [] };
  }
  const result = processSchema.safeParse(input);
  if (!result.success) return { ok: false, errors: result.error.issues.map((issue) => ({ path: issue.path.join("."), message: issue.message })), warnings: [] };
  const state = result.data, graph = state.mode === "guided" ? state.graph : state.preservedGraph;
  const issues = graph ? inspectGraph(graph, state.mode === "guided" ? "graph" : "preservedGraph") : { errors: [], warnings: [] };
  const source = state.mode === "guided" ? compileGuided(state.graph) : state.source;
  issues.errors.push(...validateSource(source));
  return issues.errors.length ? { ok: false, ...issues } : { ok: true, value: state, warnings: issues.warnings };
}
var node = (id3, type, label2, team = "", description = "") => ({ id: id3, type, label: label2, team, description });
var edge = (id3, from, to, label2 = "") => ({ id: id3, from, to, label: label2 });
var example2 = (title, nodes, edges, direction = "TB") => ({ schemaVersion: 1, tool: "process-mapper", mode: "guided", graph: { title, nodes, edges, direction } });
var processExamples = {
  onboarding: example2("Customer onboarding", [node("enquiry", "start", "New customer enquiry", "Sales"), node("details", "process", "Confirm needs and details", "Sales"), node("approved", "decision", "Ready to get started?", "Customer"), node("account", "system", "Set up customer account", "Operations"), node("welcome", "end", "Send welcome and next steps", "Support")], [edge("e1", "enquiry", "details"), edge("e2", "details", "approved"), edge("e3", "approved", "account", "Yes"), edge("e4", "approved", "details", "Need more information"), edge("e5", "account", "welcome")]),
  approval: example2("Purchase approval", [node("request", "start", "Team submits purchase request", "Team"), node("budget", "decision", "Within approved budget?", "Manager"), node("review", "process", "Review exception", "Finance"), node("order", "system", "Create purchase order", "Purchasing"), node("received", "end", "Confirm delivery", "Team")], [edge("e1", "request", "budget"), edge("e2", "budget", "order", "Yes"), edge("e3", "budget", "review", "No"), edge("e4", "review", "budget", "Revised request"), edge("e5", "order", "received")]),
  service: example2("Service-request handling", [node("ticket", "start", "Customer reports an issue", "Customer"), node("triage", "process", "Review and assign request", "Support"), node("fix", "process", "Investigate and resolve", "Service team"), node("check", "decision", "Problem resolved?", "Customer"), node("close", "end", "Close request and record solution", "Support")], [edge("e1", "ticket", "triage"), edge("e2", "triage", "fix"), edge("e3", "fix", "check"), edge("e4", "check", "fix", "No"), edge("e5", "check", "close", "Yes")]),
  systems: example2("Website, CRM and accounting", [node("web", "system", "Company website", "Marketing"), node("crm", "store", "Customer records in CRM", "Sales"), node("review", "process", "Approve customer order", "Operations"), node("accounts", "system", "Accounting software", "Finance"), node("receipt", "end", "Customer receives confirmation", "Customer")], [edge("e1", "web", "crm", "Enquiry details"), edge("e2", "crm", "review", "Order details"), edge("e3", "review", "accounts", "Approved order"), edge("e4", "accounts", "receipt", "Invoice or receipt")], "LR")
};

// cli/tools-cli.ts
init_validation();

// cli/capabilities.ts
var CLI_RELEASE = "1.0.0";
var cliCapabilities = {
  release: CLI_RELEASE,
  schemaVersion: 1,
  runtime: "Node.js 24 LTS",
  telemetry: false,
  hostedApi: false,
  mcp: false,
  commands: [
    "capabilities",
    "validate <document|plan|process> --input <file|->",
    "document pdf --input <file|-> --output <file.pdf> [--force]",
    "plan calculate --input <file|->",
    "plan export --input <file|-> --format <json|csv|pdf> --output <file> [--force]",
    "process render --input <file|-> --format <mermaid|svg|png|pdf|json> --output <file> [--force]"
  ],
  formats: { document: ["pdf"], plan: ["json", "csv", "pdf"], process: ["mermaid", "svg", "png", "pdf", "json"] },
  exitCodes: { success: 0, runtimeFailure: 1, invalidInput: 2 },
  browserRequirement: "Process SVG/PNG/PDF rendering uses an installed local Playwright Chromium. Run npm run browsers once. No external rendering service is used."
};

// cli/local-render.ts
import { createServer } from "node:http";
import { readFile, readdir } from "node:fs/promises";
import { extname, join } from "node:path";
import { chromium } from "playwright";
async function renderLocalProcess(state, format, projectRoot) {
  const directory = join(projectRoot, "dist", "render");
  const assets = /* @__PURE__ */ new Map();
  for (const entry of await readdir(directory, { withFileTypes: true })) {
    if (entry.isFile() && [".js", ".css", ".wasm"].includes(extname(entry.name))) assets.set(`/${entry.name}`, { data: await readFile(join(directory, entry.name)), type: entry.name.endsWith(".js") ? "text/javascript" : entry.name.endsWith(".css") ? "text/css" : "application/wasm" });
  }
  assets.set("/tools-assets/fonts/NotoSans-Regular.ttf", { data: await readFile(join(projectRoot, "public", "tools-assets", "fonts", "NotoSans-Regular.ttf")), type: "font/ttf" });
  assets.set("/", { data: '<!doctype html><html lang="en"><head><meta charset="UTF-8"><title>Local process renderer</title></head><body><main id="stage"></main><script type="module" src="/renderer.js"></script></body></html>', type: "text/html;charset=utf-8" });
  const server = createServer((request, response) => {
    const resource = request.method === "GET" ? assets.get(request.url ?? "") : void 0;
    response.setHeader("Cache-Control", "no-store");
    response.setHeader("Referrer-Policy", "no-referrer");
    response.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'; connect-src 'self'; img-src 'self' blob: data:; font-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'");
    if (!resource) {
      response.writeHead(404);
      response.end("Not found");
      return;
    }
    response.writeHead(200, { "Content-Type": resource.type });
    response.end(resource.data);
  });
  await new Promise((resolve2, reject) => {
    server.once("error", reject);
    server.listen(0, "127.0.0.1", resolve2);
  });
  let browser;
  try {
    const address = server.address();
    if (!address || typeof address === "string") throw new Error("Could not start the local renderer.");
    const origin = `http://127.0.0.1:${address.port}`;
    browser = await chromium.launch({ headless: true });
    const context = await browser.newContext({ viewport: { width: 1600, height: 1200 }, serviceWorkers: "block" });
    const blocked = [];
    await context.route("**/*", (route) => {
      const url = route.request().url();
      if (url.startsWith(`${origin}/`)) return route.continue();
      blocked.push("external_request");
      return route.abort();
    });
    const page = await context.newPage();
    page.setDefaultTimeout(3e4);
    await page.goto(origin);
    await page.waitForFunction(() => typeof window.essentialRender === "function");
    const result = await page.evaluate(async ({ state: state2, format: format2 }) => window.essentialRender(state2, format2), { state, format });
    if (blocked.length) throw new Error("The renderer attempted a blocked non-local request.");
    return new Uint8Array(result.data);
  } finally {
    await browser?.close();
    await new Promise((resolve2, reject) => server.close((error) => error ? reject(error) : resolve2()));
  }
}

// cli/tools-cli.ts
var CliError = class extends Error {
  constructor(message, exitCode = 2, code = "invalid_input", issues = []) {
    super(message);
    this.exitCode = exitCode;
    this.code = code;
    this.issues = issues;
  }
  exitCode;
  code;
  issues;
};
var root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
var json = (value) => `${JSON.stringify(value, null, 2)}
`;
var help = `Essential Tools local CLI ${cliCapabilities.release}

${cliCapabilities.commands.map((command) => `  node dist/cli.mjs ${command}`).join("\n")}

--input - reads JSON from stdin (maximum 3 MB). Outputs are never overwritten unless --force is present.
Process SVG/PNG/PDF needs local Chromium: npm run browsers. No telemetry or external rendering.
Exit codes: 0 success; 2 invalid input/usage; 1 runtime/export failure. Diagnostics are JSON on stderr.
`;
function parseArgs(args) {
  const positional = [];
  const flags = /* @__PURE__ */ new Map();
  for (let index = 0; index < args.length; index++) {
    const arg = args[index];
    if (!arg.startsWith("--")) {
      positional.push(arg);
      continue;
    }
    if (!["--input", "--output", "--format", "--force"].includes(arg)) throw new CliError(`Unknown option ${arg}. Use --help.`);
    if (flags.has(arg)) throw new CliError(`Option ${arg} was supplied more than once.`);
    if (arg === "--force") flags.set(arg, true);
    else {
      const value = args[++index];
      if (!value || value.startsWith("--")) throw new CliError(`${arg} needs a value.`);
      flags.set(arg, value);
    }
  }
  return { positional, flags };
}
async function inputJson(path) {
  let bytes;
  if (path === "-") {
    const chunks = [];
    let size = 0;
    for await (const chunk of process.stdin) {
      const data = new Uint8Array(chunk);
      size += data.byteLength;
      if (size > 3e6) throw new CliError("Input exceeds the 3 MB limit.");
      chunks.push(data);
    }
    bytes = Buffer.concat(chunks);
  } else {
    try {
      if ((await stat(resolve(path))).size > 3e6) throw new CliError("Input exceeds the 3 MB limit.");
      bytes = await readFile2(resolve(path));
    } catch (error) {
      if (error instanceof CliError) throw error;
      throw new CliError("The input file could not be read.", 1, "read_failed");
    }
  }
  if (bytes.byteLength > 3e6) throw new CliError("Input exceeds the 3 MB limit.");
  try {
    return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
  } catch {
    throw new CliError("Input must be a UTF-8 JSON object.");
  }
}
async function assertDifferentInput(inputPath, outputPath) {
  if (inputPath === "-") return;
  const input = resolve(inputPath);
  const identity = (path) => process.platform === "win32" ? path.toLowerCase() : path;
  let same = identity(input) === identity(outputPath);
  try {
    const [inputReal, outputReal, inputStat, outputStat] = await Promise.all([realpath(input), realpath(outputPath), stat(input, { bigint: true }), stat(outputPath, { bigint: true })]);
    same ||= identity(inputReal) === identity(outputReal) || inputStat.ino !== 0n && inputStat.dev === outputStat.dev && inputStat.ino === outputStat.ino;
  } catch (error) {
    if (error.code !== "ENOENT") throw error;
  }
  if (same) throw new CliError("Choose a different output path; the input file will not be overwritten.");
}
async function main() {
  const args = process.argv.slice(2);
  if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
    process.stdout.write(help);
    return;
  }
  const { positional, flags } = parseArgs(args);
  if (positional.length === 1 && positional[0] === "capabilities") {
    if (flags.size) throw new CliError("capabilities does not take options.");
    process.stdout.write(json(cliCapabilities));
    return;
  }
  if (positional.length !== 2) throw new CliError("Choose a documented command. Use --help.");
  const [command, operation] = positional;
  const inputPath = flags.get("--input");
  if (typeof inputPath !== "string") throw new CliError("--input <file|-> is required.");
  const data = await inputJson(inputPath);
  const kind = command === "validate" ? operation : command;
  if (!["document", "plan", "process"].includes(kind)) throw new CliError("Choose document, plan or process.");
  const checked = kind === "document" ? validateDocument(data) : kind === "plan" ? validatePlan(data) : validateProcess(data);
  if (!checked.ok) throw new CliError("The input did not pass validation.", 2, "validation_failed", checked.errors);
  if (command === "validate") {
    process.stdout.write(json({ ok: true, tool: kind, schemaVersion: 1, warnings: checked.warnings }));
    return;
  }
  if (command === "plan" && operation === "calculate") {
    const result2 = validatePlan(data);
    if (!result2.ok) throw new CliError("Invalid plan.");
    process.stdout.write(json({ ok: true, ...calculatePlan(result2.value), warnings: result2.warnings }));
    return;
  }
  const output = flags.get("--output");
  if (typeof output !== "string" || output === "-") throw new CliError("--output <file> is required for exports.");
  const outputPath = resolve(output);
  await assertDifferentInput(inputPath, outputPath);
  let format = flags.get("--format");
  let result;
  const pdfAssets = async () => ({ fontBytes: new Uint8Array(await readFile2(resolve(root, "public/tools-assets/fonts/NotoSans-Regular.ttf"))) });
  if (command === "document" && operation === "pdf") {
    if (format && format !== "pdf") throw new CliError("document pdf only exports PDF.");
    format = "pdf";
    const valid = validateDocument(data);
    if (!valid.ok) throw new CliError("Invalid document.");
    result = await generateDocumentPdf(valid.value, await pdfAssets());
  } else if (command === "plan" && operation === "export") {
    if (!["json", "csv", "pdf"].includes(String(format))) throw new CliError("Plan format must be json, csv or pdf.");
    const valid = validatePlan(data);
    if (!valid.ok) throw new CliError("Invalid plan.");
    result = format === "json" ? json(valid.value) : format === "csv" ? exportPlanCsv(valid.value) : await generatePlanPdf(valid.value, await pdfAssets());
  } else if (command === "process" && operation === "render") {
    if (!["json", "mermaid", "svg", "png", "pdf"].includes(String(format))) throw new CliError("Process format must be json, mermaid, svg, png or pdf.");
    const valid = validateProcess(data);
    if (!valid.ok) throw new CliError("Invalid process.");
    if (format === "json") result = json(valid.value);
    else if (format === "mermaid") result = `${valid.value.mode === "guided" ? compileGuided(valid.value.graph) : valid.value.source}
`;
    else result = await renderLocalProcess(valid.value, format, root);
  } else throw new CliError("Unknown command. Use --help.");
  try {
    let handle;
    try {
      handle = await open(outputPath, flags.has("--force") ? "r+" : "wx");
    } catch (error) {
      if (!flags.has("--force") || error.code !== "ENOENT") throw error;
      handle = await open(outputPath, "wx");
    }
    try {
      if (inputPath !== "-") {
        const [inputStat, outputStat] = await Promise.all([stat(resolve(inputPath), { bigint: true }), handle.stat({ bigint: true })]);
        if (inputStat.ino !== 0n && inputStat.dev === outputStat.dev && inputStat.ino === outputStat.ino) throw new CliError("Choose a different output path; the input file will not be overwritten.");
      }
      await assertDifferentInput(inputPath, outputPath);
      await handle.truncate(0);
      await handle.writeFile(result);
    } finally {
      await handle.close();
    }
  } catch (error) {
    if (error instanceof CliError) throw error;
    if (error.code === "EEXIST") throw new CliError("The output file already exists. Choose a new path or explicitly use --force.", 2, "output_exists");
    throw new CliError("The output could not be written. Check the path and directory permissions.", 1, "write_failed");
  }
  process.stdout.write(json({ ok: true, format, output: outputPath, bytes: typeof result === "string" ? Buffer.byteLength(result) : result.byteLength, warnings: checked.warnings }));
}
main().catch((error) => {
  const known = error instanceof CliError;
  const message = known || error instanceof InputError ? error.message : /Executable doesn't exist/.test(String(error?.message)) ? "Local Chromium is missing. Run npm run browsers, then retry." : "The local export failed. Check the installation and input, then retry. No output was reported as complete.";
  process.stderr.write(json({ ok: false, error: { code: known ? error.code : "runtime_failure", message, ...known && error.issues.length ? { issues: error.issues } : {} } }));
  process.exitCode = known ? error.exitCode : 1;
});
