From 78ef15345ab2ddfe526af728f940bd99f631994a Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Sun, 18 Oct 2026 09:22:53 +0700 Subject: [PATCH] docs(development): describe the plugin train and the v3 manifest --- customization/settings.mdx | 9 -- development/plugin-development.mdx | 4 +- development/plugin-registry.mdx | 191 +++++++++++------------------ development/testing-plugins.mdx | 9 +- security/privacy.mdx | 6 +- 5 files changed, 78 insertions(+), 141 deletions(-) diff --git a/customization/settings.mdx b/customization/settings.mdx index 151c6135..63ea5672 100644 --- a/customization/settings.mdx +++ b/customization/settings.mdx @@ -110,12 +110,3 @@ Three tabs are not in the table. Stock keyboard chords are on [Keyboard Shortcut ## Where settings live The file is `~/Library/Preferences/com.TablePro.plist`. **Reset All Settings to Defaults** on the General tab puts every tab back to its stock value after a confirmation, and that includes emptying the AI provider list. Saved connections, query history, and MCP tokens survive it. - -Two settings have no control in the window: - -| Setting | Command | -|---------|---------| -| Custom plugin registry | `defaults write com.TablePro com.TablePro.customRegistryURL ` | -| Clear custom registry | `defaults delete com.TablePro com.TablePro.customRegistryURL` | - -See [Plugin Registry](/development/plugin-registry#custom-registry-url) for what a private registry has to serve. diff --git a/development/plugin-development.mdx b/development/plugin-development.mdx index f11d545c..f376ffe1 100644 --- a/development/plugin-development.mdx +++ b/development/plugin-development.mdx @@ -15,8 +15,8 @@ A plugin is a macOS loadable bundle target with `WRAPPER_EXTENSION = tableplugin |-----|------|----------|---------| | `TableProPluginKitVersion` | integer | Yes | The PluginKit ABI the plugin was built against. Current value: 39 | | `TableProProvidesDatabaseTypeIds` | array of strings | Recommended | Database type IDs the plugin serves, which is what makes lazy loading possible | -| `CFBundleShortVersionString` | string | Yes | Plugin version, read by registry update checks | -| `TableProMinAppVersion` | string | No | The loader rejects the plugin on an older app | +| `CFBundleShortVersionString` | string | Yes | Plugin version shown in **Settings > Plugins** | +| `CFBundleVersion` | string | Yes | Build number. Of two copies of a plugin the higher build loads, and a registry update needs a higher build | Leave `TableProProvidesDatabaseTypeIds` out and the plugin loads eagerly at startup, blocking launch, and `PluginManager` logs a warning naming the key. With it, the app registers your metadata from `Info.plist` and loads the binary on first use. diff --git a/development/plugin-registry.mdx b/development/plugin-registry.mdx index f473f984..ad4b3bee 100644 --- a/development/plugin-registry.mdx +++ b/development/plugin-registry.mdx @@ -1,157 +1,104 @@ --- title: Plugin Registry -description: Registry manifest format, binary selection, publishing, and PluginKit compatibility +description: How registry plugins ship with each TablePro release, the v3 manifest, and how the app picks a release --- -The official manifest is `plugins.json` in [TableProApp/plugins](https://github.com/TableProApp/plugins), and TablePro's release workflow writes it: a plugin gets there by being contributed to TablePro's own repository and tagged there. The app loads only bundles TablePro signed, so there is no other route. For what install and update look like to a user, see [Plugins](/features/plugins); to build the plugin in the first place, [Plugin Development](/development/plugin-development). +Registry plugins ride the app's release train. One `v*` tag builds TablePro and every registry plugin, the plugin zips land on the app's GitHub release, and each plugin's version and build equal the app's. There are no plugin tags and no separate publish step. For what install and update look like to a user, see [Plugins](/features/plugins); to build the plugin in the first place, [Plugin Development](/development/plugin-development). -The app fetches the manifest to install a driver when someone picks a database type with no plugin loaded, to install Parquet from the Export dialog, and to offer updates. +The app reads the manifest to install a driver when someone picks a database type with no plugin loaded, to install an export format from the Export dialog, and to update installed registry plugins. -## Manifest format +## Getting a plugin into the registry -```json -{ - "schemaVersion": 2, - "plugins": [ … ] -} -``` - -Schema version 2 is current. A manifest declaring a higher version is rejected and the app falls back to its cached copy. - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `id` | string | Yes | Bundle identifier, such as `com.TablePro.OracleDriver` | -| `name` | string | Yes | Display name | -| `version` | string | Yes | Semantic version | -| `summary` | string | Yes | One-line description | -| `author` | object | Yes | `{ "name": "…", "url": "…" }`, `url` optional | -| `homepage` | string | No | Project URL | -| `category` | string | Yes | `database-driver`, `export-format`, `import-format`, `other`. An unknown value reads as `other` | -| `databaseTypeIds` | [string] | No | `DatabaseType.pluginTypeId` values, which is what drives auto-install | -| `binaries` | [object] | Yes | Per-architecture binaries | -| `minAppVersion` | string | No | Below this the install fails before any download | -| `iconName` | string | No | SF Symbol or bundled icon name | -| `isVerified` | bool | No | Defaults to `false` | -| `metadata` | object | No | Self-describing plugin metadata | - -Each entry in `binaries`: +Contribute it to TablePro's repository, as a target in the `RegistryPlugins` aggregate of `project.yml` and an entry in `.github/plugin-registry.json` that gives its display name and icon. The next release publishes it: a beta tag publishes to the `beta` channel, a stable tag to `stable`. A fix to a plugin ships the same way, in the next beta or patch. -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `architecture` | string | Yes | `arm64` or `x86_64` | -| `pluginKitVersion` | int | Yes for drivers | The PluginKit ABI the binary was built against | -| `downloadURL` | string | Yes | Direct URL to the `.zip` | -| `sha256` | string | Yes | SHA-256 hex of the ZIP | +Bundled plugins ship inside the app and never appear in the manifest. The app never installs a registry copy over a bundled one. - -v1 manifests carried top-level `downloadURL`, `sha256`, and `minPluginKitVersion` in place of `binaries`. The app still decodes them, synthesizing one entry per architecture. Write new entries with `binaries`. - +## The manifest -## Binary selection - -The app filters `binaries` to the running architecture, keeps those whose `pluginKitVersion` falls inside `[minimumCompatiblePluginKitVersion, currentPluginKitVersion]`, and installs the highest. Both bounds are declared in `PluginManager.swift`. A driver binary with no `pluginKitVersion` never resolves, and the install fails with `noCompatibleBinary`. - -## Example entry +`https://updates.tablepro.app/plugins/v3.json`, served by GitHub Pages from the `feeds` branch that the release workflow writes through `scripts/release/registry.py`. The schema version is part of the path, so a later schema ships beside this one instead of replacing it. Within v3, changes only add fields. ```json { - "id": "com.TablePro.OracleDriver", - "name": "Oracle Driver", - "version": "1.0.26", - "summary": "Oracle Database 12c+ driver via OracleNIO", - "author": { "name": "TablePro", "url": "https://tablepro.app" }, - "homepage": "https://docs.tablepro.app/databases/oracle", - "category": "database-driver", - "databaseTypeIds": ["Oracle"], - "binaries": [ - { - "architecture": "arm64", - "pluginKitVersion": 39, - "downloadURL": "https://github.com/TableProApp/TablePro/releases/download/plugin-oracle-v1.0.26/OracleDriver-arm64.zip", - "sha256": "" - }, + "schemaVersion": 3, + "retainFromPluginKit": 39, + "plugins": [ { - "architecture": "x86_64", - "pluginKitVersion": 39, - "downloadURL": "https://github.com/TableProApp/TablePro/releases/download/plugin-oracle-v1.0.26/OracleDriver-x86_64.zip", - "sha256": "" + "id": "com.TablePro.OracleDriver", + "name": "Oracle Driver", + "iconName": "oracle-icon", + "databaseTypeIds": ["Oracle"], + "releases": [ + { + "version": "0.82.0", + "build": 820099, + "channel": "stable", + "pluginKit": 39, + "minimumSystemVersion": "13.0", + "url": "https://github.com/TableProApp/TablePro/releases/download/v0.82.0/OracleDriver-0.82.0.zip", + "sha256": "" + } + ] } - ], - "minAppVersion": "0.57.0", - "iconName": "server.rack", - "isVerified": true + ] } ``` -## Plugin metadata +Plugins are sorted by `id`, and each plugin's releases by `build`, highest first. The app refuses a document whose `schemaVersion` is not 3 and keeps the copy it already has. -The optional `metadata` object makes an entry self-describing, so the app renders the connection form, sidebar, and editor for a database type before the plugin is installed. Carry it on every driver entry: without it, someone picking your database type stares at a bare form until the download finishes. +| Plugin field | Type | Description | +|-------|------|-------------| +| `id` | string | Bundle identifier | +| `name` | string | Display name, from `.github/plugin-registry.json` | +| `iconName` | string | SF Symbol or bundled icon name | +| `databaseTypeIds` | [string] | The bundle's `TableProProvidesDatabaseTypeIds`. Picking one of these types installs the plugin | +| `exportFormatIds` | [string] | Optional. The bundle's `TableProProvidesExportFormatIds`, which the Export dialog offers to install | +| `releases` | [object] | Published builds | -| Group | Fields | -|-------|--------| -| Identity and form | `displayName`, `iconName`, `defaultPort`, `brandColorHex`, `connectionMode`, `requiresAuthentication`, `additionalConnectionFields`, `postConnectActions`, `urlSchemes`, `fileExtensions` | -| Capabilities | `supportsSSH`, `supportsSSL`, `supportsForeignKeys`, `supportsSchemaEditing`, `supportsDatabaseSwitching`, `supportsImport`, `supportsExport`, `supportsReadOnlyMode`, `supportsHealthMonitor`, and the rest of the flags | -| Naming and navigation | `systemDatabaseNames`, `systemSchemaNames`, `defaultSchemaName`, `tableEntityName`, `containerEntityName`, `navigationModel`, `databaseGroupingStrategy` | -| Editor | `editorLanguage`, `queryLanguageName`, `sqlDialect` (keywords, functions, data types, pagination style), `statementCompletions`, `explainVariants`, `columnTypesByCategory` | +`name`, `iconName` and the ids come from the plugin's highest build, so an older release never rewrites them. -`RegistryPluginMetadata` in `TablePro/Core/Plugins/Registry/RegistryModels.swift` is the full field list. `update-registry.py` copies an existing `metadata` block forward on every release, so it is edited by hand in the registry repository and never regenerated. +| Release field | Type | Description | +|-------|------|-------------| +| `version` | string | The app version that built it, such as `0.82.0` or `0.83.0-beta.1` | +| `build` | integer | The app's `CFBundleVersion` | +| `channel` | string | `stable` or `beta` | +| `pluginKit` | integer | The bundle's `TableProPluginKitVersion` | +| `minimumSystemVersion` | string | The bundle's `LSMinimumSystemVersion` | +| `url` | string | The zip on the app's GitHub release. Must be `https` | +| `sha256` | string | SHA-256 hex of the zip, checked after the download | -## Publishing a plugin +## How the app picks a release -Every plugin CI can publish has an entry in `.github/plugin-registry.json` in TablePro's repository, keyed by the slug that appears in its tag. That file maps the slug to a build target, so the mapping has no derivation rule: `mssql` builds `MSSQLDriver` and `cloudflare-d1` builds `CloudflareD1DriverPlugin`. Add the entry before the first tag, or the workflow exits with `Unknown plugin`. + + + An app whose version carries `-beta.N`, and a local build, take `stable` and `beta` releases. A stable app takes `stable` only. A channel the app does not know is skipped, and the rest of the manifest still loads. + + + Releases whose `minimumSystemVersion` is above the running macOS are skipped. + + + `pluginKit` has to fall between `TABLEPRO_PLUGINKIT_MINIMUM_VERSION` and `TABLEPRO_PLUGINKIT_VERSION` in `Configs/PluginKit.xcconfig`. + + + The highest `build` left wins. + + -```bash -git tag -a plugin-oracle-v1.0.26 -m "plugin-oracle-v1.0.26" -git push origin plugin-oracle-v1.0.26 -``` - - -Push plugin tags one at a time. A push carrying more than three tags creates no push events on GitHub, so no workflow fires and nothing is published. - +When nothing is left, the install fails with **This plugin needs a newer version of TablePro. Update TablePro, then install it again.** if a release with a newer PluginKit exists, and with **No build of this plugin works with this version of TablePro.** otherwise. -Dispatching the workflow works too. Its `tags` input takes comma-separated `tag:pluginKitVersion` pairs, and dropping the `:` part makes the workflow read `currentPluginKitVersion` from `PluginManager.swift`: +The zip must match `sha256`, and the bundle inside must carry TablePro's Team ID in its code signature. -```bash -gh workflow run build-plugin.yml --field "tags=plugin-oracle-v1.0.26" -``` +## Retention -Either way CI builds both architectures, signs and notarizes the bundles, checks each bundle's declared PluginKit version against the release label, creates the GitHub release as a draft, checks the uploaded zips byte for byte, publishes it, re-verifies the published assets, and pushes the updated `plugins.json` to TableProApp/plugins through `.github/scripts/update-registry.py`, which writes atomically and rebases on a retry when the matrix jobs collide. +`registry.py` keeps the two highest builds for each combination of channel, `pluginKit` and `minimumSystemVersion`. A beta goes once a stable with the same combination has a higher build, and a release whose `pluginKit` is below `retainFromPluginKit` goes too. A release that retention would drop on arrival is logged and not added. -A published release is immutable, and a rebuild never matches its bytes. A run for a tag whose release is already published with other zips fails, so a new binary ships under the next patch version. +`retainFromPluginKit` is the PluginKit version of the oldest app version that still runs on at least 1% of the Macs seen in the last 30 days. It rises through a normal pull request, and the manifest keeps the higher of its current value and the one in the tag, so a patch on an older line never lowers it. -Bundled plugins ride with the app release. Eight of them also have an entry in `.github/plugin-registry.json` (SQLite, ClickHouse, Redis, XLSX export, XLSX import, MQL export, SQL import, Markdown export), so a fix to one can be tagged and published to the registry. The app never installs or offers a registry update over a bundled plugin, so that build reaches only users who download its zip and install it from a file. Everyone else gets the fix with the next app release. A bulk ABI re-release skips them. The other seven have no entry and never reach `plugins.json`. +Withdrawing a version removes its releases. The build retention kept before it comes back into selection. ## PluginKit compatibility -A plugin built against any PluginKit version inside the app's `[minimum, current]` range loads, and the runtime fills newer requirements from their defaults. What that means for releases: - -- **Additive change** (a new requirement with a default, a new field on a non-frozen type): raise `currentPluginKitVersion` and the `TableProPluginKitVersion` of every plugin rebuilt against it, leave `minimumCompatiblePluginKitVersion` alone, and re-publish nothing. The binaries already out there keep serving, because the runtime fills a requirement they predate from its default. The bump is for the other direction: a plugin rebuilt against the new SDK references symbols an older app does not carry, and a manifest left at the old number passes that app's version check and then fails to load at all. Leaving the manifest behind is the one mistake this rule exists to stop. -- **Breaking change** (a removed or changed requirement, a frozen-layout change, a requirement without a default): raise `currentPluginKitVersion` and `minimumCompatiblePluginKitVersion` together, then run `scripts/release-all-plugins.sh `. It reads the registry-only plugins out of `.github/plugin-registry.json`, bumps each one's patch version, and fires a single `workflow_dispatch` so they all build as one matrix. -- **Retention**: `update-registry.py` keeps binaries for the three newest PluginKit versions per plugin. Older ones are pruned, and an app below every remaining binary resolves nothing at all, for every plugin. -- **Bump the kit at most once per release cycle.** Retention counts kit versions, not days, so the window's length in wall-clock time is set by how often the number moves. Eleven bumps between 2026-09-02 and 2026-09-12 took it from 20 to 30 and left the oldest published binary at kit 21, while v0.65.0 to v0.70.0 ship kit 19 and v0.71.0 ships kit 20. - -To reach users who have not updated, build the plugin against the release they are on rather than against `main`: - -```bash -scripts/release-plugin-for-shipped-app.sh plugin-mongodb-v1.0.45 v0.73.0 -``` - -It reads that tag's `currentPluginKitVersion` and dispatches the workflow with `baseRef` set, so the binary links against that release's PluginKit and declares the matching `TableProPluginKitVersion`. Both have to come from the same tree. Stamping a binary built from `main` with an older number makes the older app accept it and then fail `Bundle.loadAndReturnError`. - -The app's own release workflow runs `scripts/check-registry-readiness.py --floor --current ` and fails until every registry driver has a compatible binary, so the app cannot ship ahead of its plugins. When an installed driver predates a breaking bump, the app repairs it in the background on the next connect. See [After an app update](/features/plugins#after-an-app-update). +A plugin loads when its `TableProPluginKitVersion` falls inside the app's range. An additive change raises `TABLEPRO_PLUGINKIT_VERSION`; a breaking change raises `TABLEPRO_PLUGINKIT_MINIMUM_VERSION` with it. Every release rebuilds every registry plugin against the PluginKit it ships, so neither needs a separate re-release. An installed driver that predates a breaking bump is repaired in the background. See [After an app update](/features/plugins#after-an-app-update). ## Caching -The app fetches the manifest from `raw.githubusercontent.com/TableProApp/plugins/main/plugins.json`, which caches at the edge for about five minutes. Every fetch revalidates conditionally, the list refreshes at launch and when **Settings > Plugins** opens (throttled to one check per five minutes), and an install prompt forces a fresh fetch before it reports a plugin missing. CI also purges the jsDelivr cache after each registry push, for older app versions that still fetch from there. A newly published plugin shows up in the app within minutes. - -## Custom registry URL - -Point the app at a private or enterprise mirror of the manifest: - -```bash -defaults write com.TablePro com.TablePro.customRegistryURL "https://your-registry.example.com/plugins.json" - -defaults delete com.TablePro com.TablePro.customRegistryURL -``` - -The custom manifest replaces the official one rather than adding to it. HTTP caching keys on the full URL, so a changed registry URL takes effect on the next fetch. A mirror can serve only bundles TablePro signed, since the app refuses every other signature. Both commands are also listed in [Settings](/customization/settings). +GitHub Pages serves the manifest with `Cache-Control: max-age=600`. The app keeps the response in its HTTP cache on disk and decodes it at launch, so the catalog is there before any request. At launch and when **Settings > Plugins** opens, the cached response answers while it is fresh. Before an install reports a plugin missing, and when the app repairs an outdated plugin, it revalidates with the server. A new release reaches the app within about ten minutes. diff --git a/development/testing-plugins.mdx b/development/testing-plugins.mdx index 04f0c79e..de04dcd5 100644 --- a/development/testing-plugins.mdx +++ b/development/testing-plugins.mdx @@ -14,11 +14,11 @@ A plugin's signature is checked only outside the app's own `PlugIns` directory. | `PlugIns/` inside the app bundle | Built-in | `Info.plist` versions only | | `~/Library/Application Support/TablePro/Plugins` | User-installed | `Info.plist` versions, then the code signature | -The version keys go first either way: a plugin whose `TableProPluginKitVersion` falls outside the app's compatible range, or whose `TableProMinAppVersion` is newer than the running app, is rejected before anything looks at its signature. +The version key goes first either way: a plugin whose `TableProPluginKitVersion` falls outside the app's compatible range is rejected before anything looks at its signature. The signature check accepts one signer: the Apple team that signed the running app, which for a release is TablePro. Your own debug build accepts what your own Xcode signed for the same reason. Every other signature, ad-hoc signing and an unsigned bundle are refused, and nothing asks. -Do not reuse a built-in plugin's bundle ID. Installing over one fails outright, and at discovery the higher version wins, with the built-in taking a tie and the losing user copy deleted from disk. +Do not reuse a built-in plugin's bundle ID. Installing over one fails outright, and at discovery the higher build (`CFBundleVersion`) wins, with the built-in taking a tie and the losing user copy deleted from disk. ## Install with the dev script @@ -26,10 +26,10 @@ Do not reuse a built-in plugin's bundle ID. Installing over one fails outright, scripts/install-plugin-dev.sh MyDriverPlugin ``` -It finds the newest `MyDriverPlugin.tableplugin` under DerivedData, copies it into the user plugins directory, and writes the registry metadata sidecar. Relaunch TablePro to load it. Build from Xcode first: the script reads DerivedData, and a command-line `xcodebuild` writes to `build/Debug` instead, where the script never looks. +It finds the newest `MyDriverPlugin.tableplugin` under DerivedData and copies it into the user plugins directory. Relaunch TablePro to load it. Build from Xcode first: the script reads DerivedData, and a command-line `xcodebuild` writes to `build/Debug` instead, where the script never looks. -Never edit the copied bundle, `Info.plist` included. Any change invalidates the code signature and dyld then refuses to load the executable. If the plugin's `TableProMinAppVersion` is ahead of your built app, raise the app's `MARKETING_VERSION` in `Configs/Version.xcconfig` and rebuild the app. +Never edit the copied bundle, `Info.plist` included. Any change invalidates the code signature and dyld then refuses to load the executable. ## Bundle it into the app instead @@ -67,7 +67,6 @@ The reason lands in a **Plugin Installation Failed** alert when installing, and | **Plugin code signature verification failed: bundle is not signed by TablePro** | A valid signature from a team other than the running app's | Sign with the app's team, or set `TABLEPRO_ALLOW_UNSIGNED_PLUGINS=1` in a Debug build | | **Plugin code signature verification failed: …** | Unsigned, ad-hoc signed, modified after signing, or a symbolic link that points outside the bundle | Rebuild rather than patch. Any edit to a signed bundle breaks the seal | | **Plugin was built for PluginKit version …; this release of TablePro needs version …** | The bundle predates a breaking ABI bump | Rebuild the plugin against the current TableProPluginKit | -| **Plugin requires app version … or later, but current version is …** | `TableProMinAppVersion` is ahead of the app | Raise `MARKETING_VERSION` in `Configs/Version.xcconfig` and rebuild | | **A built-in plugin "…" already provides this bundle ID** | The bundle ID collides with a bundled plugin | Give your plugin its own bundle ID | Once it loads and works, publish it through the [Plugin Registry](/development/plugin-registry) so users get a signed binary. diff --git a/security/privacy.mdx b/security/privacy.mdx index 79723a2c..302e7a59 100644 --- a/security/privacy.mdx +++ b/security/privacy.mdx @@ -9,7 +9,7 @@ Three requests leave a stock install without you asking: one usage heartbeat a d | --- | --- | --- | --- | | [Usage heartbeat](#usage-heartbeat) | `api.tablepro.app` | Yes | **Settings > General**, under Privacy | | [Update check](#update-checks) | `updates.tablepro.app` | Yes | **Settings > General**, under Software Update | -| [Plugin catalog](#the-plugin-catalog) | `raw.githubusercontent.com` | Yes | None | +| [Plugin catalog](#the-plugin-catalog) | `updates.tablepro.app` | Yes | None | | [AI provider](#ai-providers) | whichever provider you add | No provider is configured on a fresh install | Delete the provider, or turn off **Enable AI Features** | | [iCloud sync](#icloud-sync) | your own iCloud account | No | **Settings > Sync** | | [MCP clients](#the-mcp-server) | nowhere: it listens on `127.0.0.1` | No | **Settings > MCP** | @@ -118,7 +118,7 @@ Updates download from the GitHub release named in the feed. Sparkle checks the f ## The plugin catalog -At launch the app fetches `https://raw.githubusercontent.com/TableProApp/plugins/main/plugins.json` when its cached copy is more than five minutes old, and again when it reconciles a plugin that a newer app version rejected. The request adds no header of its own; GitHub sees the IP address and the user agent URLSession sends. The response is cached on disk. +At launch the app asks for `https://updates.tablepro.app/plugins/v3.json`, and its HTTP cache answers without a request while the last response is under ten minutes old. It asks the server again before it reports a plugin missing, and when it repairs a plugin that a newer app version rejected. The request adds no header of its own; GitHub Pages sees the IP address and the user agent URLSession sends. The response is cached on disk. Installing a plugin downloads its ZIP over HTTPS from the URL in that catalog and checks it against the SHA-256 the catalog declares. @@ -175,7 +175,7 @@ Every path here is under `~/Library/Application Support/TablePro/` unless it say | `FilterState/`, `ColumnLayout/` | Per-table filters and column widths. | | `LastOpenConnections.json` | What to reopen for **Reopen Last Session**. | | `Plugins/` | Plugins you installed yourself. | -| `Registry/registry-manifest.json`, `Registry/URLCache/` | The cached plugin catalog. | +| `Registry/URLCache/` | The cached plugin catalog. | | `team_library.json` | The cached team library. | | `linked_sql_index.db` | File metadata for linked SQL folders. | | `~/Library/Preferences/com.TablePro.plist` | Settings, and the signed license blob, which carries the email on the license. |