Metadata-Version: 2.4
Name: pgprofiler
Version: 1.0.1
Summary: Backend of PgFlow-ai, a PostgreSQL IDE with the PgFlow-ai Profiler extension (Electron desktop app in desktop/)
Author-email: CandorLight LLC <support@pgflow-ai.com>
Maintainer-email: CandorLight LLC <support@pgflow-ai.com>
License-Expression: LicenseRef-CandorLight-Proprietary
Project-URL: Homepage, https://pgflow-ai.com/
Classifier: Private :: Do Not Upload
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: psycopg[binary]>=3.1
Requires-Dist: langchain>=1.0
Requires-Dist: langchain-openai>=1.0
Requires-Dist: langchain-anthropic>=1.0
Requires-Dist: langchain-google-genai>=3.0
Requires-Dist: langchain-ollama>=1.0
Requires-Dist: mcp>=2.0
Requires-Dist: httpx>=0.27
Requires-Dist: websockets>=13
Requires-Dist: cryptography>=42
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# PgFlow-ai (with the PgFlow-ai Profiler extension)

*A product of [CandorLight LLC](https://pgflow-ai.com/).*

**PgFlow-ai** is the desktop IDE for PostgreSQL in `desktop/` (Electron + Vue 3 + Element Plus, Python
backend). It covers database development, design, data management, administration and analysis; the
**PgFlow-ai Profiler** described further below is one of its extensions, and every profiler feature is available
in the studio (Profiler menu, trace windows open as documents).

| Area | Tools |
| --- | --- |
| Workspace | Database Explorer (connections, every object type, context menus, filter, system objects), Properties panel, document tabs, Start Page, Find Object (names and source code), light/dark themes |
| SQL development | SQL editor (PostgreSQL syntax, completion from the live catalog, snippets, formatter, comment toggle), execute script / current statement / selection, cancel, server-side cursor paging, auto-commit or manual transactions, inline error markers, notices, estimated and actual plan diagrams, **Query Profiler** (profile runs and compare them), result export, Query History, Snippets Manager, **AI Assistant** (generate / explain / optimize / fix SQL with schema context) |
| Database design | **Table Designer** (columns, constraints, indexes, options, live ALTER/CREATE script), **Database Diagram** (ER diagram with auto layout, crow's-foot relationships, draw foreign keys, PNG/SVG export), visual **Query Builder**, object viewer (definition, properties, dependencies), Script As CREATE / DROP / SELECT / INSERT / UPDATE / DELETE, schema scripts |
| Data management | **Data Editor** (paging, filter, sort, edit / insert / delete with SQL preview, record view, foreign-key navigation), **Master-Detail Browser**, **Data Compare** + sync, **Schema Compare** + sync, **Import** (CSV, JSON, XLSX; append / upsert / skip) and **Export** (CSV, text, JSON, SQL, XML, HTML, Markdown, XLSX), **Data Generator** |
| Administration | **Server Monitor** (live charts, sessions with cancel/terminate, lock chains, top queries, table / index health, settings), **Security Manager** (roles, membership, object and default privileges), **Backup / Restore** (pg_dump / pg_restore / psql), New Database / Schema, maintenance (VACUUM, ANALYZE, REINDEX) |
| Analysis & reporting | **Pivot Table** (drag-and-drop, charts), **Data Reports** (grouped, printable), **Database Documenter** (HTML / Markdown) |
| Profiler extension | SQL Server Profiler style tracing, templates, filters, grouping, replay, execution plans and the AI Advisor (see below) |

Not included: a step-through PL/pgSQL debugger (it needs the `pldebugger` server extension).

![Start page and Database Explorer](docs/screenshots/studio_start.png)
![SQL editor: results and foreign-key JOIN completion](docs/screenshots/sql_editor.png)
![Actual execution plan](docs/screenshots/execution_plan.png)
![Database diagram](docs/screenshots/er_diagram.png)
![Schema Compare](docs/screenshots/schema_compare.png)
![Server Monitor under load](docs/screenshots/server_monitor.png)

```bash
cd desktop
npm install          # Node.js 20+; also downloads the Electron binary
npm run dev          # hot-reloading development run
npm run build && npm start   # production build
```

The backend (`pgprofiler/server` + `pgprofiler/studio`) runs with the project's `.venv` interpreter (create it with
`uv sync`); set `PGPROFILER_PYTHON` to use another one. Backup / Restore use `pg_dump`, `pg_restore`
and `psql` from PATH or the folder chosen in the Backup document.

### Licensing

The application needs a license key. Without a valid key (missing, expired, issued for another computer, or the
clock set back), the backend refuses everything except the license screen. Keys are Ed25519-signed and checked
offline (`pgprofiler/licensing.py`, which holds only the public key). A key either never expires or has a last
valid day, and is locked to one or more **machine IDs**. The customer's machine ID is shown on the license screen
and under Help ▸ License.

Customers normally never see a key. The license screen fetches keys online from the **licensing API** in `server/`
(`pgprofiler/license_online.py`; set `PGFLOW_LICENSE_API` to use another server):

* **Start free trial** gets a 14-day key for this machine. Each machine gets one trial.
* **Activate with code** takes the code from the purchase email (`PGF-…`) and gets a perpetual key for this machine.
* **Deactivate this computer** frees the seat so the license can move to another computer.

Offline computers get their key on the website's activation page and paste it in. The key is checked offline in
every case. The commercial website is in `website/` (static Astro). `website_pure_html/` is the same site as plain HTML
files with relative links (`npm run html` in `website/` regenerates it). For WordPress + Divi 5 there are two
versions, each with its own plugin: `website_pure_html_divi/` puts website_pure_html's HTML into Divi pages (looks
identical), and `website_divi/` rebuilds the pages from Divi's own modules. The API contract is in
[`docs/commercial/api-contract.md`](docs/commercial/api-contract.md).

Keys can also be made by hand with the **license generator** in `licensegen/`. It is an internal tool and is not part of the
installers:

```bash
uv run python -m licensegen                 # desktop generator: Issue / Licenses / Verify
uv run python -m licensegen issue --type trial --name "Jane Doe" --email jane@example.com --machine ABCDE-FGHIJ-KLMNO-PQRST
uv run python -m licensegen issue --type pro --name "ACME Ltd" --machine ABCDE-FGHIJ-KLMNO-PQRST --out acme.lic
uv run python -m licensegen machine-id      # this computer's id (e.g. for a developer key)
```

The private signing key and the history of issued keys live in `~/.config/pgflow-ai-licensegen/`
(`%APPDATA%\PgFlow-ai License Generator` on Windows, or `$LICENSEGEN_HOME`). **Back up `signing_key.pem`.**
Released builds accept only keys signed with it. If it is lost, every installed copy needs a new build before
it can accept new keys.

---

# PgFlow-ai Profiler

A desktop trace tool for PostgreSQL (Linux, Windows and macOS) modelled on **Microsoft SQL Server Profiler**: connect to a
server, pick a trace template, watch events stream into a grid, filter/group/find them, save traces
to files or tables, extract the SQL, and replay it.

![Profiler trace with captured bind parameters](docs/screenshots/profiler_trace.png)

PostgreSQL has no SQL Trace / Extended Events API, so the profiler builds its event stream from three
complementary sources and maps everything onto Profiler-style *event classes* and *data columns*:

| Source | What it gives | Requirements |
| --- | --- | --- |
| **Server log** (`stderr`, `csvlog` or `jsonlog`) | Every statement with exact duration, prepared-statement phases with bind parameters, errors, deadlocks, lock waits, logins/logouts, checkpoints, autovacuum, auto_explain plans | Statement logging enabled (the profiler can turn it on with `ALTER SYSTEM` and restore it afterwards) and a way to read the log: `pg_read_binary_file()` (superuser / `pg_read_server_files`), a local file, or a command such as `docker logs -f` |
| **Activity sampling** (`pg_stat_activity`) | Sessions, running/completed statements, long-running statements, lock blocking with blocking PIDs, idle-in-transaction | Nothing beyond `pg_read_all_stats` (works out of the box; very short statements can be missed between samples) |
| **Statement statistics** (`pg_stat_statements`) | Per-query deltas: calls, total time, rows, block reads/writes (the *Reads/Writes* columns) | `pg_stat_statements` in `shared_preload_libraries` |

## Features (SQL Server Profiler parity)

* **Trace Properties** dialog with *General* (name, template, save to file with max size/rollover,
  save to table, stop time), *Events Selection* (event classes grouped by category with per-column
  check boxes, *Show all events / columns*, *Column Filters...*, *Organize Columns...*) and a
  PostgreSQL-specific *Capture* tab (log source, activity sampler, statistics, server configuration).
* **Predefined templates**: Standard, SQL, SQL_Duration, SQL_Grouped, SQL_Locks, SQL_Replay, SQL_SPs,
  Tuning, Errors_and_Warnings, Activity_Sampler (zero configuration), Server_Health, Blank. User
  templates (`.pgtdf`) can be created, edited, imported/exported, derived from a running trace and set
  as default.
* **Column filters** with the Profiler operators (*Like / Not like* for text, *Equals / Not equal /
  Greater than or equal / Less than or equal* for numbers and times, *Exclude rows that do not contain
  values*), applied at capture time; plus post-capture *Filter displayed events* and right-click
  *Filter by this value / Exclude this value* (as in the SSMS XEvent Profiler).
* **Trace window**: live grid with auto scroll, colour-coded rows (errors, locks, logins, plans), a
  lower pane with the syntax-highlighted statement (parameters substituted for prepared statements)
  and an event-properties table, sortable columns when stopped, Find / Find Next, bookmarks,
  copy rows, **grouped and aggregated views** (Organize Columns -> Groups) with counts per group.
* **Run / Pause / Stop**, clear window, trace stop time, maximum in-memory rows, several traces in tabs.
* **Save** to `.pgtrace` files (JSON Lines, size limit + rollover) or to a PostgreSQL table; **open**
  trace files and tables later; **export** CSV / JSON / **SQL script** (extract the statements).
* **Replay** captured statements against any server: one connection per original session (SPID) to
  keep transaction semantics, original timing (with speed factor) or as fast as possible, single step,
  run to cursor, breakpoints, pause on error, results pane.
* **Server Configuration** tool: inspect and change the logging parameters, apply "full tracing"
  settings, reset them, create `pg_stat_statements`.
* Saved connection profiles, keyboard shortcuts (F5 run, F6 pause, F7 stop, Ctrl+F find, F3 next,
  Ctrl+F2 bookmark, F9 breakpoint, F10 step, Ctrl+G grouped view).

![Trace properties](docs/screenshots/trace_properties.png)

## Execution plans and the AI advisor

Select a statement in the grid and open the **Execution Plan** tab in the bottom pane (or press
**Ctrl+L** for the estimated plan, **Ctrl+M** for the actual plan, or use the *Profiler* menu / the
row's context menu). PostgreSQL's equivalent of SQL Server's showplan is `EXPLAIN`, so the tab runs

* **Estimated Plan** - `EXPLAIN (SETTINGS, FORMAT JSON)`; nothing is executed. Statements captured
  through the extended protocol with unlogged parameters use `EXPLAIN (GENERIC_PLAN)` (PostgreSQL 16+).
* **Actual Plan** - `EXPLAIN (ANALYZE, BUFFERS, WAL, FORMAT JSON)` inside a transaction that is always
  rolled back, so even INSERT/UPDATE/DELETE statements can be analysed without keeping their effect.

Logged bind parameters (`Extended:*` events) are substituted first. Plans logged by `auto_explain`
(Showplan events) are shown directly. The plan is drawn SQL Server style - root on the left, data
flowing right to left, edge width proportional to the row count - with per-node exclusive cost/time
percentages, a properties panel, and heuristic warnings (bad row estimates, sequential scans that
filter most rows, sorts/hashes spilling to disk, stale visibility maps, unlaunched parallel workers,
...). Text and JSON views are available too.

**AI Advisor** (the tab's *Ask AI Advisor* button, or *Profiler > Ask AI Advisor*) sends the statement,
the captured event details and the plan to a language model of your choice and shows its diagnosis
plus a table of concrete recommendations - create/drop/alter index, query rewrite, statistics,
configuration, vacuum - each with the SQL, a rollback statement, an impact and a risk rating. Select a
recommendation and click **Apply...** (the SQL is shown for editing and confirmation, then executed
on the traced server), **Roll back...**, **Copy SQL**, or **Explain this query** for rewrites. After a
change is applied the plan is re-run and the status line compares cost and execution time before and
after. Follow-up questions keep the conversation context.

![AI Advisor: diagnosis and recommendations for a captured statement](docs/screenshots/ai_advisor.png)

The advisor is a LangChain tool-calling agent. Configure it in *Tools > AI Settings*:

* **Provider / model** - Anthropic Claude, OpenAI, Google Gemini, DeepSeek, Kimi, Qwen, any
  OpenAI-compatible endpoint (LM Studio, vLLM, ...), Ollama Cloud or a local Ollama. API keys are kept
  obfuscated in `<config dir>/ai.json` (mode 0600). *Refresh list* fetches the provider's live model
  list, *Test connection* sends a one-word prompt.
* **Database tools (MCP)** - the model inspects the live database through a PostgreSQL
  [Model Context Protocol](https://modelcontextprotocol.io) server started per question with the
  trace's connection string: [Postgres MCP Pro](https://github.com/crystaldba/postgres-mcp)
  (`uvx`, default; schema/objects, EXPLAIN with hypothetical indexes when `hypopg` is installed, index
  and workload analysis, `pg_stat_statements`, health checks) or
  [mcp-postgres-server](https://github.com/antonorlov/mcp-postgres-server) (`npx`), or a custom
  command. The default access mode is read-only. Built-in read-only tools (schemas, tables, column
  statistics, indexes, settings, EXPLAIN, ad-hoc `SELECT`) are always available as well, so the
  advisor works even without `uvx`/`npx`.
* The model never executes DDL/DML itself: every change goes through the Apply dialog.
* While a question is running the tab shows a "Working on it" bar with the current step. When the model
  returns no answer, times out or the provider is overloaded, the whole run (MCP server and agent) is
  restarted automatically, up to *Retries on transient failures* times.

*View > Theme* (or *Tools > Options*) switches between the system look, Light and Dark. New traces
start from the event/column/filter selection of the previous trace (*Tools > Options*), so the
selection does not have to be repeated every time.

## Where the website and API live

`endpoints.env` at the repository root is the one place for the website and license API addresses
(`PGFLOW_SITE_URL`, `PGFLOW_API_URL`). The website build, the desktop app (and through it the Python backend) and the
four installers (`tools/build_*.sh`) all read it. For a quick local test, edit it or override it for one command:

```bash
PGFLOW_SITE_URL=http://localhost:8080 PGFLOW_API_URL=http://localhost:5080 tools/build_deb.sh
```

`website/publish.sh` refuses to upload a site or installers built for another address than the server's. The API
(`server/`) keeps its own settings (CORS origins, checkout return pages) in its appsettings files.

## Installation (from source)

Requirements: Linux, Windows or macOS, Python 3.10+, Node.js 20+, a PostgreSQL 12+ server (tested against 13, 16
and 17). Backup / Restore need the PostgreSQL client tools (`pg_dump`, `pg_restore`, `psql`) of the server's major
version or newer.

```bash
uv sync                      # backend: creates .venv with its dependencies (or: python -m venv .venv && .venv/bin/pip install -e .)
cd desktop
npm install                  # also downloads the Electron binary
npm run build && npm start   # or `npm run dev` for a hot-reloading development run
```

Electron starts the backend with `python -m pgprofiler.server` (a JSON-RPC server on a 127.0.0.1 WebSocket,
protected by a per-launch token) using the project's `.venv` interpreter, or the one in `PGPROFILER_PYTHON`. On
quit, running traces are stopped and the server settings they changed are restored before the backend exits. If
the backend stops unexpectedly, the open documents are kept and *Restart backend* starts a new one.

### Installers

All installers are built on Linux and are self-contained (Electron runtime, a private CPython and the backend's
packages; psycopg brings its own libpq), so the target machine needs nothing else installed:

```bash
./tools/build_deb.sh   # -> dist/pgflow-ai_<version>_amd64.deb   (Debian / Ubuntu)
./tools/build_msi.sh   # -> dist/PgFlow-ai-<version>-x64.msi     (Windows 10/11 x64)
./tools/build_dmg.sh   # -> dist/PgFlow-ai-<version>-arm64.dmg   (macOS 13+, Apple silicon)
                       #    dist/PgFlow-ai-<version>-x64.dmg     (macOS 13+, Intel); `build_dmg.sh arm64` for one
(cd website && npm run downloads)   # copy the new sizes and SHA-256 checksums into the website
```

* **.deb** installs into `/opt/pgflow-ai` with `/usr/bin/pgflow-ai`, a desktop entry, icons and
  AppStream metadata: `sudo apt install ./dist/pgflow-ai_*.deb`. It recommends `postgresql-client` for
  Backup / Restore.
* **.msi** installs into `C:\Program Files\PgFlow-ai` with a Start Menu shortcut, an Add/Remove Programs
  entry and in-place upgrades; the last page offers *Launch PgFlow-ai now*. It is written by `wixl`
  (msitools; fetched with `apt-get download` when it is not installed) — no Windows machine is needed.
* **.dmg** holds `PgFlow-ai.app` and an Applications link to drag it to. The bundle is Electron for macOS (renamed,
  with its helper apps), a python-build-standalone CPython and the macOS wheels (resolved wheels-only per
  architecture). No Mac is needed: [rcodesign](https://github.com/indygreg/apple-platform-rs) signs, `genisoimage`
  writes the image and [libdmg-hfsplus](https://github.com/fanquake/libdmg-hfsplus) compresses it (both tools are
  fetched or built into `build/cache` on first use). Without a certificate the app is **ad-hoc signed**: it runs,
  but macOS asks the user to allow it once (*System Settings ▸ Privacy & Security ▸ Open Anyway*). For releases,
  set `APPLE_P12_FILE` + `APPLE_P12_PASSWORD_FILE` (Developer ID Application certificate, hardened runtime with
  `packaging/macos/entitlements.plist`) and `APPLE_API_KEY_FILE` (App Store Connect key) to sign and notarize.

Requirements on the build machine: `desktop/node_modules` installed (`npm install`), [uv](https://docs.astral.sh/uv/),
`curl`, `dpkg-deb` (for the .deb), a CPython 3.12 for precompiling the Windows bytecode (optional), and for the .dmg
`unzip`, `genisoimage`, `git`, `gcc` and the zlib headers.

## Quick start

1. **Profiler > New Trace...** (Ctrl+Alt+N, or *Profiler* in the toolbar): pick or enter a connection (profiles
   are shared with the Database Explorer). The
   Trace Properties dialog opens with the default template (Options can skip it and start immediately).
2. Choose a template and adjust events/columns; on the **Capture** tab check the log source:
   * *Automatic* reads the server log through SQL when `logging_collector = on` and the role may
     read server files; otherwise pick a *local file* (`/var/log/postgresql/...`) or a *command*
     (for Docker the dialog suggests `docker logs -f --tail 0 <container>`).
   * *Enable required logging settings automatically* lets the profiler run `ALTER SYSTEM` for
     the parameters the selected events need (`log_statement`, `log_min_duration_statement`,
     `log_connections`, `log_lock_waits`, ...) plus a machine-readable `log_line_prefix`, and restore
     them when the trace stops. Untick it on servers you must not reconfigure - the
     *Activity_Sampler* template works without any server change.
3. Press **Run**. Generate some load, e.g. `python tools/generate_load.py "host=localhost user=postgres password=..."`
   (inserts, prepared statements, a slow query, an error, a lock wait, a deadlock and a statement timeout).
4. Stop the trace, group it (Profiler > Grouped View), save it, extract the SQL or replay it.

## Event classes

| Category | Event classes | Source |
| --- | --- | --- |
| Security Audit | Audit Login, Audit Logout, Login Failed, Audit Role Change, Audit Grant Revoke, Audit Server Config | log / activity / derived |
| Sessions | ExistingConnection, Connection Received, Connection Authenticated | activity / log |
| SQL | SQL:StmtStarting, SQL:StmtCompleted, SQL:LongRunning | log / activity |
| Extended Protocol | Extended:Parse, Extended:Bind, Extended:ExecuteStarting, Extended:ExecuteCompleted, Extended:Fetch | log |
| Stored Procedures | SP:Starting, SP:Completed (CALL / DO) | derived |
| Transactions | Transaction:Begin, Commit, Rollback, Savepoint, Prepared, IdleInTransaction | derived / activity |
| Locks | Lock:Wait, Lock:Acquired, Lock:Blocked, Lock:Deadlock, Lock:Timeout | log / activity |
| Errors and Warnings | Exception, Fatal Error, Warning, Attention (cancel / timeout), Serialization Failure, User Message | log |
| Performance | Showplan (auto_explain), Statement Statistics (pg_stat_statements), Temp File | log / stats |
| Objects | Object:Created, Object:Altered, Object:Dropped | derived |
| Cursors | Cursor:Open, Cursor:Fetch, Cursor:Close | derived |
| Server | Checkpoint Starting/Complete, Autovacuum, Autoanalyze, Config Reload, Server Startup Shutdown, Server Message | log |

*Derived* events are produced by classifying the SQL text of statement events (DDL, transaction
control, CALL/DO, cursors, GRANT/REVOKE...). Hovering an event in the **Events Selection** tab of Trace
Properties shows its description, source and the server settings it needs.

Data columns follow the Profiler names: EventClass, TextData, ApplicationName, LoginName, DatabaseName,
SPID (backend PID), HostName, ClientPort, SessionID, Duration (ms), StartTime, EndTime, Reads, Writes,
RowCounts, Calls, Error (SQLSTATE), Severity, EventSubClass, ObjectName, ObjectType, TransactionID,
VirtualXID, QueryID, WaitEvent, BlockedBy, Detail, Hint, Context, BackendType, ServerName, Source,
LineNumber, IsSystem, EventSequence.

## Server settings used

| Setting | Needed for |
| --- | --- |
| `log_statement = all` | SQL:StmtStarting, Extended:Parse/Bind/ExecuteStarting |
| `log_min_duration_statement = 0` | SQL:StmtCompleted, Extended:ExecuteCompleted / Fetch (durations) |
| `log_connections = on`, `log_disconnections = on` | Audit Login / Logout, Connection Received / Authenticated |
| `log_lock_waits = on` | Lock:Wait, Lock:Acquired (after `deadlock_timeout`) |
| `log_temp_files = 0` | Temp File |
| `log_checkpoints = on`, `log_autovacuum_min_duration = 0` | Server events |
| `log_min_messages = notice` | User Message (RAISE NOTICE) |
| `auto_explain.log_min_duration = 0` | Showplan (auto_explain must be preloaded) |
| `log_line_prefix = '%m|%p|%c|%l|%q%u|%d|%h|%v|%x|%e|%Q|%b|%a|'` | Session identity in stderr logs (any prefix is parsed; this one is recommended) |

`logging_collector` and `shared_preload_libraries` need a server restart and are never changed by the
profiler. Logging every statement has a cost on busy servers: use column filters, limit the events,
or the sampler-only template on production systems - the same advice Microsoft gives for Profiler.

## Project layout

```
pgprofiler/core      event catalog, event model, filters, log prefix parser, log parsers (stderr/csv/json),
                     log->event mapper, SQL classifier, templates, trace files, connections
pgprofiler/capture   collectors (log, activity, statistics), log transports, server configuration,
                     trace session orchestration, replay engine
pgprofiler/server    JSON-RPC backend (WebSocket) for the Electron desktop UI
pgprofiler/studio    studio backend: connection hub, explorer, DDL, SQL sessions, data editor, compare, transfer,
                     admin tools, design, table designer / AI assistant, data generator / pivot / reports
desktop/             Electron + Vue 3 + Element Plus desktop UI: studio shell and documents (src/renderer/src/studio),
                     profiler extension (src/renderer/src/profiler, components)
tools/               build_deb.sh / build_msi.sh (installers), generate_load.py (demo workload for the profiler)
packaging/           installer sources: linux (desktop entry, control, metainfo), windows (WiX), branding, package_tools.py
tests/               pytest suite (profiler core and capture, RPC server, every studio module, QA regressions)
```

Run the tests with `.venv/bin/python -m pytest`. Live database tests run when `PGPROFILER_TEST_DSN` points at a
disposable server (they create and drop scratch databases and roles), e.g.
`PGPROFILER_TEST_DSN="host=localhost port=5432 user=postgres password=... dbname=postgres"`.

## Trace file format

`.pgtrace` files are JSON Lines: the first line is a header (`pgtrace` version, server, the full trace
definition), then one JSON object per event with the column keys above. They can be processed with
`jq`, pandas, etc. Traces saved to a table use the same column names.

## License

Proprietary. Copyright © 2026 CandorLight LLC. All rights reserved. See [LICENSE](LICENSE).

Support: [support@pgflow-ai.com](mailto:support@pgflow-ai.com) · [pgflow-ai.com](https://pgflow-ai.com/)
